summaryrefslogtreecommitdiff
path: root/src/scaladoc/scala/tools/nsc/doc/model/diagram
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2013-03-06 07:39:19 -0800
committerAdriaan Moors <adriaan.moors@typesafe.com>2013-03-09 11:59:07 -0800
commitc6ca941ccc017a8869f4def717cfeb640f965077 (patch)
treee21038cc55d3a4231544d148913504a754eafdf8 /src/scaladoc/scala/tools/nsc/doc/model/diagram
parent9094822181c398b945b7f30ac1e2b05da9796f53 (diff)
downloadscala-c6ca941ccc017a8869f4def717cfeb640f965077.tar.gz
scala-c6ca941ccc017a8869f4def717cfeb640f965077.tar.bz2
scala-c6ca941ccc017a8869f4def717cfeb640f965077.zip
Moved scaladoc sources into separate directory.
This change is not externally visible. It moves the scaladoc sources into src/scaladoc and adds an ant target for building them. The compilation products are still packaged into scala-compiler.jar as before, but with a small change to build.xml a separate jar can be created instead.
Diffstat (limited to 'src/scaladoc/scala/tools/nsc/doc/model/diagram')
-rw-r--r--src/scaladoc/scala/tools/nsc/doc/model/diagram/Diagram.scala137
-rw-r--r--src/scaladoc/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala257
-rw-r--r--src/scaladoc/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala254
3 files changed, 648 insertions, 0 deletions
diff --git a/src/scaladoc/scala/tools/nsc/doc/model/diagram/Diagram.scala b/src/scaladoc/scala/tools/nsc/doc/model/diagram/Diagram.scala
new file mode 100644
index 0000000000..150b293b81
--- /dev/null
+++ b/src/scaladoc/scala/tools/nsc/doc/model/diagram/Diagram.scala
@@ -0,0 +1,137 @@
+package scala.tools.nsc.doc
+package model
+package diagram
+
+import model._
+
+/**
+ * The diagram base classes
+ *
+ * @author Damien Obrist
+ * @author Vlad Ureche
+ */
+abstract class Diagram {
+ def nodes: List[Node]
+ def edges: List[(Node, List[Node])]
+ def isContentDiagram = false // Implemented by ContentDiagram
+ def isInheritanceDiagram = false // Implemented by InheritanceDiagram
+ def depthInfo: DepthInfo
+}
+
+case class ContentDiagram(nodes:List[/*Class*/Node], edges:List[(Node, List[Node])]) extends Diagram {
+ override def isContentDiagram = true
+ lazy val depthInfo = new ContentDiagramDepth(this)
+}
+
+/** A class diagram */
+case class InheritanceDiagram(thisNode: ThisNode,
+ superClasses: List[/*Class*/Node],
+ subClasses: List[/*Class*/Node],
+ incomingImplicits: List[ImplicitNode],
+ outgoingImplicits: List[ImplicitNode]) extends Diagram {
+ def nodes = thisNode :: superClasses ::: subClasses ::: incomingImplicits ::: outgoingImplicits
+ def edges = (thisNode -> (superClasses ::: outgoingImplicits)) ::
+ (subClasses ::: incomingImplicits).map(_ -> List(thisNode))
+
+ override def isInheritanceDiagram = true
+ lazy val depthInfo = new DepthInfo {
+ def maxDepth = 3
+ }
+}
+
+trait DepthInfo {
+ /** Gives the maximum depth */
+ def maxDepth: Int
+}
+
+abstract class Node {
+ def name = tpe.name
+ def tpe: TypeEntity
+ def tpl: Option[TemplateEntity]
+ /** shortcut to get a DocTemplateEntity */
+ def doctpl: Option[DocTemplateEntity] = tpl match {
+ case Some(tpl) => tpl match {
+ case d: DocTemplateEntity => Some(d)
+ case _ => None
+ }
+ case _ => None
+ }
+ /* shortcuts to find the node type without matching */
+ def isThisNode = false
+ def isNormalNode = false
+ def isClassNode = if (tpl.isDefined) (tpl.get.isClass || tpl.get.qualifiedName == "scala.AnyRef") else false
+ def isTraitNode = if (tpl.isDefined) tpl.get.isTrait else false
+ def isObjectNode= if (tpl.isDefined) tpl.get.isObject else false
+ def isTypeNode = if (doctpl.isDefined) doctpl.get.isAbstractType || doctpl.get.isAliasType else false
+ def isOtherNode = !(isClassNode || isTraitNode || isObjectNode || isTypeNode)
+ def isImplicitNode = false
+ def isOutsideNode = false
+ def tooltip: Option[String]
+}
+
+// different matchers, allowing you to use the pattern matcher against any node
+// NOTE: A ThisNode or ImplicitNode can at the same time be ClassNode/TraitNode/OtherNode, not exactly according to
+// case class specification -- thus a complete match would be:
+// node match {
+// case ThisNode(tpe, _) => /* case for this node, you can still use .isClass, .isTrait and .isOther */
+// case ImplicitNode(tpe, _) => /* case for an implicit node, you can still use .isClass, .isTrait and .isOther */
+// case _ => node match {
+// case ClassNode(tpe, _) => /* case for a non-this, non-implicit Class node */
+// case TraitNode(tpe, _) => /* case for a non-this, non-implicit Trait node */
+// case OtherNode(tpe, _) => /* case for a non-this, non-implicit Other node */
+// }
+// }
+object Node { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = Some((n.tpe, n.tpl)) }
+object ClassNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isClassNode) Some((n.tpe, n.tpl)) else None }
+object TraitNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isTraitNode) Some((n.tpe, n.tpl)) else None }
+object TypeNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isTypeNode) Some((n.tpe, n.tpl)) else None }
+object ObjectNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isObjectNode) Some((n.tpe, n.tpl)) else None }
+object OutsideNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isOutsideNode) Some((n.tpe, n.tpl)) else None }
+object OtherNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isOtherNode) Some((n.tpe, n.tpl)) else None }
+
+
+
+/** The node for the current class */
+case class ThisNode(tpe: TypeEntity, tpl: Option[TemplateEntity])(val tooltip: Option[String] = None) extends Node { override def isThisNode = true }
+
+/** The usual node */
+case class NormalNode(tpe: TypeEntity, tpl: Option[TemplateEntity])(val tooltip: Option[String] = None) extends Node { override def isNormalNode = true }
+
+/** A class or trait the thisnode can be converted to by an implicit conversion
+ * TODO: I think it makes more sense to use the tpe links to templates instead of the TemplateEntity for implicit nodes
+ * since some implicit conversions convert the class to complex types that cannot be represented as a single tmeplate
+ */
+case class ImplicitNode(tpe: TypeEntity, tpl: Option[TemplateEntity])(val tooltip: Option[String] = None) extends Node { override def isImplicitNode = true }
+
+/** An outside node is shown in packages when a class from a different package makes it to the package diagram due to
+ * its relation to a class in the template (see @contentDiagram hideInheritedNodes annotation) */
+case class OutsideNode(tpe: TypeEntity, tpl: Option[TemplateEntity])(val tooltip: Option[String] = None) extends Node { override def isOutsideNode = true }
+
+
+// Computing and offering node depth information
+class ContentDiagramDepth(pack: ContentDiagram) extends DepthInfo {
+ private[this] var _maxDepth = 0
+ private[this] var _nodeDepth = Map[Node, Int]()
+ private[this] var seedNodes = Set[Node]()
+ private[this] val invertedEdges: Map[Node, List[Node]] =
+ pack.edges.flatMap({case (node: Node, outgoing: List[Node]) => outgoing.map((_, node))}).groupBy(_._1).map({case (k, values) => (k, values.map(_._2))}).withDefaultValue(Nil)
+ private[this] val directEdges: Map[Node, List[Node]] = pack.edges.toMap.withDefaultValue(Nil)
+
+ // seed base nodes, to minimize noise - they can't all have parents, else there would only be cycles
+ seedNodes ++= pack.nodes.filter(directEdges(_).isEmpty)
+
+ while (!seedNodes.isEmpty) {
+ var newSeedNodes = Set[Node]()
+ for (node <- seedNodes) {
+ val depth = 1 + (-1 :: directEdges(node).map(_nodeDepth.getOrElse(_, -1))).max
+ if (depth != _nodeDepth.getOrElse(node, -1)) {
+ _nodeDepth += (node -> depth)
+ newSeedNodes ++= invertedEdges(node)
+ if (depth > _maxDepth) _maxDepth = depth
+ }
+ }
+ seedNodes = newSeedNodes
+ }
+
+ val maxDepth = _maxDepth
+}
diff --git a/src/scaladoc/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala b/src/scaladoc/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala
new file mode 100644
index 0000000000..6395446d3b
--- /dev/null
+++ b/src/scaladoc/scala/tools/nsc/doc/model/diagram/DiagramDirectiveParser.scala
@@ -0,0 +1,257 @@
+package scala.tools.nsc.doc
+package model
+package diagram
+
+import model._
+import java.util.regex.{Pattern, Matcher}
+import scala.util.matching.Regex
+
+/**
+ * This trait takes care of parsing @{inheritance, content}Diagram annotations
+ *
+ * @author Damien Obrist
+ * @author Vlad Ureche
+ */
+trait DiagramDirectiveParser {
+ this: ModelFactory with DiagramFactory with CommentFactory with TreeFactory =>
+
+ import this.global.definitions.AnyRefClass
+
+ ///// DIAGRAM FILTERS //////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * The DiagramFilter trait directs the diagram engine about the way the diagram should be displayed
+ *
+ * Vlad: There's an explanation I owe to people using diagrams and not finding a way to hide a specific class from
+ * all diagrams at once. So why did I choose to allow you to only control the diagrams at class level? So, the
+ * reason is you would break the separate scaladoc compilation:
+ * If you have an "@diagram hideMyClass" annotation in class A and you run scaladoc on it along with its subclass B
+ * A will not appear in B's diagram. But if you scaladoc only on B, A's comment will not be parsed and the
+ * instructions to hide class A from all diagrams will not be available. Thus I prefer to force you to control the
+ * diagrams of each class locally. The problem does not appear with scalac, as scalac stores all its necessary
+ * information (like scala signatures) serialized in the .class file. But we couldn't store doc comments in the class
+ * file, could we? (Turns out we could, but that's another story)
+ *
+ * Any flaming for this decision should go to scala-internals@googlegroups.com
+ */
+ trait DiagramFilter {
+ /** A flag to hide the diagram completely */
+ def hideDiagram: Boolean
+ /** Hide incoming implicit conversions (for type hierarchy diagrams) */
+ def hideIncomingImplicits: Boolean
+ /** Hide outgoing implicit conversions (for type hierarchy diagrams) */
+ def hideOutgoingImplicits: Boolean
+ /** Hide superclasses (for type hierarchy diagrams) */
+ def hideSuperclasses: Boolean
+ /** Hide subclasses (for type hierarchy diagrams) */
+ def hideSubclasses: Boolean
+ /** Show related classes from other objects/traits/packages (for content diagrams) */
+ def hideInheritedNodes: Boolean
+ /** Hide a node from the diagram */
+ def hideNode(clazz: Node): Boolean
+ /** Hide an edge from the diagram */
+ def hideEdge(clazz1: Node, clazz2: Node): Boolean
+ }
+
+ /** Main entry point into this trait: generate the filter for inheritance diagrams */
+ def makeInheritanceDiagramFilter(template: DocTemplateImpl): DiagramFilter = {
+
+ val defaultFilter =
+ if (template.isClass || template.isTrait || template.sym == AnyRefClass)
+ FullDiagram
+ else
+ NoDiagramAtAll
+
+ if (template.comment.isDefined)
+ makeDiagramFilter(template, template.comment.get.inheritDiagram, defaultFilter, isInheritanceDiagram = true)
+ else
+ defaultFilter
+ }
+
+ /** Main entry point into this trait: generate the filter for content diagrams */
+ def makeContentDiagramFilter(template: DocTemplateImpl): DiagramFilter = {
+ val defaultFilter = if (template.isPackage || template.isObject) FullDiagram else NoDiagramAtAll
+ if (template.comment.isDefined)
+ makeDiagramFilter(template, template.comment.get.contentDiagram, defaultFilter, isInheritanceDiagram = false)
+ else
+ defaultFilter
+ }
+
+ protected var tFilter = 0l
+ protected var tModel = 0l
+
+ /** Show the entire diagram, no filtering */
+ case object FullDiagram extends DiagramFilter {
+ val hideDiagram: Boolean = false
+ val hideIncomingImplicits: Boolean = false
+ val hideOutgoingImplicits: Boolean = false
+ val hideSuperclasses: Boolean = false
+ val hideSubclasses: Boolean = false
+ val hideInheritedNodes: Boolean = false
+ def hideNode(clazz: Node): Boolean = false
+ def hideEdge(clazz1: Node, clazz2: Node): Boolean = false
+ }
+
+ /** Hide the diagram completely, no need for special filtering */
+ case object NoDiagramAtAll extends DiagramFilter {
+ val hideDiagram: Boolean = true
+ val hideIncomingImplicits: Boolean = true
+ val hideOutgoingImplicits: Boolean = true
+ val hideSuperclasses: Boolean = true
+ val hideSubclasses: Boolean = true
+ val hideInheritedNodes: Boolean = true
+ def hideNode(clazz: Node): Boolean = true
+ def hideEdge(clazz1: Node, clazz2: Node): Boolean = true
+ }
+
+ /** The AnnotationDiagramFilter trait directs the diagram engine according to an annotation
+ * TODO: Should document the annotation, for now see parseDiagramAnnotation in ModelFactory.scala */
+ case class AnnotationDiagramFilter(hideDiagram: Boolean,
+ hideIncomingImplicits: Boolean,
+ hideOutgoingImplicits: Boolean,
+ hideSuperclasses: Boolean,
+ hideSubclasses: Boolean,
+ hideInheritedNodes: Boolean,
+ hideNodesFilter: List[Pattern],
+ hideEdgesFilter: List[(Pattern, Pattern)]) extends DiagramFilter {
+
+ private[this] def getName(n: Node): String =
+ if (n.tpl.isDefined)
+ n.tpl.get.qualifiedName
+ else
+ n.name
+
+ def hideNode(clazz: Node): Boolean = {
+ val qualifiedName = getName(clazz)
+ for (hideFilter <- hideNodesFilter)
+ if (hideFilter.matcher(qualifiedName).matches) {
+ // println(hideFilter + ".matcher(" + qualifiedName + ").matches = " + hideFilter.matcher(qualifiedName).matches)
+ return true
+ }
+ false
+ }
+
+ def hideEdge(clazz1: Node, clazz2: Node): Boolean = {
+ val clazz1Name = getName(clazz1)
+ val clazz2Name = getName(clazz2)
+ for ((clazz1Filter, clazz2Filter) <- hideEdgesFilter) {
+ if (clazz1Filter.matcher(clazz1Name).matches &&
+ clazz2Filter.matcher(clazz2Name).matches) {
+ // println(clazz1Filter + ".matcher(" + clazz1Name + ").matches = " + clazz1Filter.matcher(clazz1Name).matches)
+ // println(clazz2Filter + ".matcher(" + clazz2Name + ").matches = " + clazz2Filter.matcher(clazz2Name).matches)
+ return true
+ }
+ }
+ false
+ }
+ }
+
+ // TODO: This could certainly be improved -- right now the only regex is *, but there's no way to match a single identifier
+ private val NodeSpecRegex = "\\\"[A-Za-z\\*][A-Za-z\\.\\*]*\\\""
+ private val NodeSpecPattern = Pattern.compile(NodeSpecRegex)
+ private val EdgeSpecRegex = "\\(" + NodeSpecRegex + "\\s*\\->\\s*" + NodeSpecRegex + "\\)"
+ // And the composed regexes:
+ private val HideNodesRegex = new Regex("^hideNodes(\\s*" + NodeSpecRegex + ")+$")
+ private val HideEdgesRegex = new Regex("^hideEdges(\\s*" + EdgeSpecRegex + ")+$")
+
+ private def makeDiagramFilter(template: DocTemplateImpl,
+ directives: List[String],
+ defaultFilter: DiagramFilter,
+ isInheritanceDiagram: Boolean): DiagramFilter = directives match {
+
+ // if there are no specific diagram directives, return the default filter (either FullDiagram or NoDiagramAtAll)
+ case Nil =>
+ defaultFilter
+
+ // compute the exact filters. By including the annotation, the diagram is autmatically added
+ case _ =>
+ tFilter -= System.currentTimeMillis
+ var hideDiagram0: Boolean = false
+ var hideIncomingImplicits0: Boolean = false
+ var hideOutgoingImplicits0: Boolean = false
+ var hideSuperclasses0: Boolean = false
+ var hideSubclasses0: Boolean = false
+ var hideInheritedNodes0: Boolean = false
+ var hideNodesFilter0: List[Pattern] = Nil
+ var hideEdgesFilter0: List[(Pattern, Pattern)] = Nil
+
+ def warning(message: String) = {
+ // we need the position from the package object (well, ideally its comment, but yeah ...)
+ val sym = if (template.sym.isPackage) template.sym.info.member(global.nme.PACKAGE) else template.sym
+ assert((sym != global.NoSymbol) || (sym == global.rootMirror.RootPackage))
+ global.reporter.warning(sym.pos, message)
+ }
+
+ def preparePattern(className: String) =
+ "^" + className.stripPrefix("\"").stripSuffix("\"").replaceAll("\\.", "\\\\.").replaceAll("\\*", ".*") + "$"
+
+ // separate entries:
+ val entries = directives.foldRight("")(_ + " " + _).split(",").map(_.trim)
+ for (entry <- entries)
+ entry match {
+ case "hideDiagram" =>
+ hideDiagram0 = true
+ case "hideIncomingImplicits" if isInheritanceDiagram =>
+ hideIncomingImplicits0 = true
+ case "hideOutgoingImplicits" if isInheritanceDiagram =>
+ hideOutgoingImplicits0 = true
+ case "hideSuperclasses" if isInheritanceDiagram =>
+ hideSuperclasses0 = true
+ case "hideSubclasses" if isInheritanceDiagram =>
+ hideSubclasses0 = true
+ case "hideInheritedNodes" if !isInheritanceDiagram =>
+ hideInheritedNodes0 = true
+ case HideNodesRegex(last) =>
+ val matcher = NodeSpecPattern.matcher(entry)
+ while (matcher.find()) {
+ val classPattern = Pattern.compile(preparePattern(matcher.group()))
+ hideNodesFilter0 ::= classPattern
+ }
+ case HideEdgesRegex(last) =>
+ val matcher = NodeSpecPattern.matcher(entry)
+ while (matcher.find()) {
+ val class1Pattern = Pattern.compile(preparePattern(matcher.group()))
+ assert(matcher.find()) // it's got to be there, just matched it!
+ val class2Pattern = Pattern.compile(preparePattern(matcher.group()))
+ hideEdgesFilter0 ::= ((class1Pattern, class2Pattern))
+ }
+ case "" =>
+ // don't need to do anything about it
+ case _ =>
+ warning("Could not understand diagram annotation in " + template.kind + " " + template.qualifiedName +
+ ": unmatched entry \"" + entry + "\".\n" +
+ " This could be because:\n" +
+ " - you forgot to separate entries by commas\n" +
+ " - you used a tag that is not allowed in the current context (like @contentDiagram hideSuperclasses)\n"+
+ " - you did not use one of the allowed tags (see docs.scala-lang.org for scaladoc annotations)")
+ }
+ val result =
+ if (hideDiagram0)
+ NoDiagramAtAll
+ else if ((hideNodesFilter0.isEmpty) &&
+ (hideEdgesFilter0.isEmpty) &&
+ (hideIncomingImplicits0 == false) &&
+ (hideOutgoingImplicits0 == false) &&
+ (hideSuperclasses0 == false) &&
+ (hideSubclasses0 == false) &&
+ (hideInheritedNodes0 == false) &&
+ (hideDiagram0 == false))
+ FullDiagram
+ else
+ AnnotationDiagramFilter(
+ hideDiagram = hideDiagram0,
+ hideIncomingImplicits = hideIncomingImplicits0,
+ hideOutgoingImplicits = hideOutgoingImplicits0,
+ hideSuperclasses = hideSuperclasses0,
+ hideSubclasses = hideSubclasses0,
+ hideInheritedNodes = hideInheritedNodes0,
+ hideNodesFilter = hideNodesFilter0,
+ hideEdgesFilter = hideEdgesFilter0)
+
+ if (settings.docDiagramsDebug.value && result != NoDiagramAtAll && result != FullDiagram)
+ settings.printMsg(template.kind + " " + template.qualifiedName + " filter: " + result)
+ tFilter += System.currentTimeMillis
+
+ result
+ }
+}
diff --git a/src/scaladoc/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala b/src/scaladoc/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala
new file mode 100644
index 0000000000..ebac25bbe4
--- /dev/null
+++ b/src/scaladoc/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala
@@ -0,0 +1,254 @@
+package scala.tools.nsc.doc
+package model
+package diagram
+
+import model._
+
+// statistics
+import html.page.diagram.DiagramStats
+
+import scala.collection.immutable.SortedMap
+
+/**
+ * This trait takes care of generating the diagram for classes and packages
+ *
+ * @author Damien Obrist
+ * @author Vlad Ureche
+ */
+trait DiagramFactory extends DiagramDirectiveParser {
+ this: ModelFactory with ModelFactoryTypeSupport with DiagramFactory with CommentFactory with TreeFactory =>
+
+ import this.global.definitions._
+ import this.global._
+
+ // the following can used for hardcoding different relations into the diagram, for bootstrapping purposes
+ def aggregationNode(text: String) =
+ NormalNode(new TypeEntity { val name = text; val refEntity = SortedMap[Int, (base.LinkTo, Int)]() }, None)()
+
+ /** Create the inheritance diagram for this template */
+ def makeInheritanceDiagram(tpl: DocTemplateImpl): Option[Diagram] = {
+
+ tFilter = 0
+ tModel = -System.currentTimeMillis
+
+ // the diagram filter
+ val diagramFilter = makeInheritanceDiagramFilter(tpl)
+
+ def implicitTooltip(from: DocTemplateEntity, to: TemplateEntity, conv: ImplicitConversion) =
+ Some(from.qualifiedName + " can be implicitly converted to " + conv.targetType + " by the implicit method "
+ + conv.conversionShortName + " in " + conv.convertorOwner.kind + " " + conv.convertorOwner.qualifiedName)
+
+ val result =
+ if (diagramFilter == NoDiagramAtAll)
+ None
+ else {
+ // the main node
+ val thisNode = ThisNode(tpl.resultType, Some(tpl))(Some(tpl.qualifiedName + " (this " + tpl.kind + ")"))
+
+ // superclasses
+ val superclasses: List[Node] =
+ tpl.parentTypes.collect {
+ case p: (TemplateEntity, TypeEntity) if !classExcluded(p._1) => NormalNode(p._2, Some(p._1))()
+ }.reverse
+
+ // incoming implcit conversions
+ lazy val incomingImplicitNodes = tpl.incomingImplicitlyConvertedClasses.map {
+ case (incomingTpl, conv) =>
+ ImplicitNode(makeType(incomingTpl.sym.tpe, tpl), Some(incomingTpl))(implicitTooltip(from=incomingTpl, to=tpl, conv=conv))
+ }
+
+ // subclasses
+ var subclasses: List[Node] =
+ tpl.directSubClasses.collect {
+ case d: TemplateImpl if !classExcluded(d) => NormalNode(makeType(d.sym.tpe, tpl), Some(d))()
+ }.sortBy(_.tpl.get.name)(implicitly[Ordering[String]].reverse)
+
+ // outgoing implicit coversions
+ lazy val outgoingImplicitNodes = tpl.outgoingImplicitlyConvertedClasses.map {
+ case (outgoingTpl, outgoingType, conv) =>
+ ImplicitNode(outgoingType, Some(outgoingTpl))(implicitTooltip(from=tpl, to=tpl, conv=conv))
+ }
+
+ // TODO: Everyone should be able to use the @{inherit,content}Diagram annotation to change the diagrams.
+ // Currently, it's possible to leave nodes and edges out, but there's no way to create new nodes and edges
+ // The implementation would need to add the annotations and the logic to select nodes (or create new ones)
+ // and add edges to the diagram -- I bet it wouldn't take too long for someone to do it (one or two days
+ // at most) and it would be a great add to the diagrams.
+ if (tpl.sym == AnyRefClass)
+ subclasses = List(aggregationNode("All user-defined classes and traits"))
+
+ val filteredSuperclasses = if (diagramFilter.hideSuperclasses) Nil else superclasses
+ val filteredIncomingImplicits = if (diagramFilter.hideIncomingImplicits) Nil else incomingImplicitNodes
+ val filteredSubclasses = if (diagramFilter.hideSubclasses) Nil else subclasses
+ val filteredImplicitOutgoingNodes = if (diagramFilter.hideOutgoingImplicits) Nil else outgoingImplicitNodes
+
+ // final diagram filter
+ filterDiagram(InheritanceDiagram(thisNode, filteredSuperclasses.reverse, filteredSubclasses.reverse, filteredIncomingImplicits, filteredImplicitOutgoingNodes), diagramFilter)
+ }
+
+ tModel += System.currentTimeMillis
+ DiagramStats.addFilterTime(tFilter)
+ DiagramStats.addModelTime(tModel-tFilter)
+
+ result
+ }
+
+ /** Create the content diagram for this template */
+ def makeContentDiagram(pack: DocTemplateImpl): Option[Diagram] = {
+
+ tFilter = 0
+ tModel = -System.currentTimeMillis
+
+ // the diagram filter
+ val diagramFilter = makeContentDiagramFilter(pack)
+
+ val result =
+ if (diagramFilter == NoDiagramAtAll)
+ None
+ else {
+ var mapNodes = Map[TemplateEntity, Node]()
+ var nodesShown = Set[TemplateEntity]()
+ var edgesAll = List[(TemplateEntity, List[TemplateEntity])]()
+
+ // classes is the entire set of classes and traits in the package, they are the superset of nodes in the diagram
+ // we collect classes, traits and objects without a companion, which are usually used as values(e.g. scala.None)
+ val nodesAll = pack.members collect {
+ case d: TemplateEntity if ((!diagramFilter.hideInheritedNodes) || (d.inTemplate == pack)) => d
+ }
+
+ // for each node, add its subclasses
+ for (node <- nodesAll if !classExcluded(node)) {
+ node match {
+ case dnode: MemberTemplateImpl =>
+ var superClasses = dnode.parentTypes.map(_._1).filter(nodesAll.contains(_))
+
+ // TODO: Everyone should be able to use the @{inherit,content}Diagram annotation to add nodes to diagrams.
+ if (pack.sym == ScalaPackage)
+ if (dnode.sym == NullClass)
+ superClasses = List(makeTemplate(AnyRefClass))
+ else if (dnode.sym == NothingClass)
+ superClasses = (List(NullClass) ::: ScalaValueClasses).map(makeTemplate(_))
+
+ if (!superClasses.isEmpty) {
+ nodesShown += dnode
+ nodesShown ++= superClasses
+ }
+ edgesAll ::= dnode -> superClasses
+ case _ =>
+ }
+
+ mapNodes += node -> (
+ if (node.inTemplate == pack && (node.isDocTemplate || node.isAbstractType || node.isAliasType))
+ NormalNode(node.resultType, Some(node))()
+ else
+ OutsideNode(node.resultType, Some(node))()
+ )
+ }
+
+ if (nodesShown.isEmpty)
+ None
+ else {
+ val nodes = nodesAll.filter(nodesShown.contains(_)).flatMap(mapNodes.get(_))
+ val edges = edgesAll.map(pair => (mapNodes(pair._1), pair._2.map(mapNodes(_)))).filterNot(pair => pair._2.isEmpty)
+ val diagram =
+ // TODO: Everyone should be able to use the @{inherit,content}Diagram annotation to change the diagrams.
+ if (pack.sym == ScalaPackage) {
+ // Tried it, but it doesn't look good:
+ // var anyRefSubtypes: List[Node] = List(mapNodes(makeTemplate(AnyRefClass)))
+ // var dirty = true
+ // do {
+ // val length = anyRefSubtypes.length
+ // anyRefSubtypes :::= edges.collect { case p: (Node, List[Node]) if p._2.exists(anyRefSubtypes.contains(_)) => p._1 }
+ // anyRefSubtypes = anyRefSubtypes.distinct
+ // dirty = (anyRefSubtypes.length != length)
+ // } while (dirty)
+ // println(anyRefSubtypes)
+ val anyRefSubtypes = Nil
+ val allAnyRefTypes = aggregationNode("All AnyRef subtypes")
+ val nullTemplate = makeTemplate(NullClass)
+ if (nullTemplate.isDocTemplate)
+ ContentDiagram(allAnyRefTypes::nodes, (mapNodes(nullTemplate), allAnyRefTypes::anyRefSubtypes)::edges.filterNot(_._1.tpl == Some(nullTemplate)))
+ else
+ ContentDiagram(nodes, edges)
+ } else
+ ContentDiagram(nodes, edges)
+
+ filterDiagram(diagram, diagramFilter)
+ }
+ }
+
+ tModel += System.currentTimeMillis
+ DiagramStats.addFilterTime(tFilter)
+ DiagramStats.addModelTime(tModel-tFilter)
+
+ result
+ }
+
+ /** Diagram filtering logic */
+ private def filterDiagram(diagram: Diagram, diagramFilter: DiagramFilter): Option[Diagram] = {
+ tFilter -= System.currentTimeMillis
+
+ val result =
+ if (diagramFilter == FullDiagram)
+ Some(diagram)
+ else if (diagramFilter == NoDiagramAtAll)
+ None
+ else {
+ // Final diagram, with the filtered nodes and edges
+ diagram match {
+ case InheritanceDiagram(thisNode, _, _, _, _) if diagramFilter.hideNode(thisNode) =>
+ None
+
+ case InheritanceDiagram(thisNode, superClasses, subClasses, incomingImplicits, outgoingImplicits) =>
+
+ def hideIncoming(node: Node): Boolean =
+ diagramFilter.hideNode(node) || diagramFilter.hideEdge(node, thisNode)
+
+ def hideOutgoing(node: Node): Boolean =
+ diagramFilter.hideNode(node) || diagramFilter.hideEdge(thisNode, node)
+
+ // println(thisNode)
+ // println(superClasses.map(cl => "super: " + cl + " " + hideOutgoing(cl)).mkString("\n"))
+ // println(subClasses.map(cl => "sub: " + cl + " " + hideIncoming(cl)).mkString("\n"))
+ Some(InheritanceDiagram(thisNode,
+ superClasses.filterNot(hideOutgoing(_)),
+ subClasses.filterNot(hideIncoming(_)),
+ incomingImplicits.filterNot(hideIncoming(_)),
+ outgoingImplicits.filterNot(hideOutgoing(_))))
+
+ case ContentDiagram(nodes0, edges0) =>
+ // Filter out all edges that:
+ // (1) are sources of hidden classes
+ // (2) are manually hidden by the user
+ // (3) are destinations of hidden classes
+ val edges: List[(Node, List[Node])] =
+ diagram.edges.flatMap({
+ case (source, dests) if !diagramFilter.hideNode(source) =>
+ val dests2 = dests.collect({ case dest if (!(diagramFilter.hideEdge(source, dest) || diagramFilter.hideNode(dest))) => dest })
+ if (dests2 != Nil)
+ List((source, dests2))
+ else
+ Nil
+ case _ => Nil
+ })
+
+ // Only show the the non-isolated nodes
+ // TODO: Decide if we really want to hide package members, I'm not sure that's a good idea (!!!)
+ // TODO: Does .distinct cause any stability issues?
+ val sourceNodes = edges.map(_._1)
+ val sinkNodes = edges.map(_._2).flatten
+ val nodes = (sourceNodes ::: sinkNodes).distinct
+ Some(ContentDiagram(nodes, edges))
+ }
+ }
+
+ tFilter += System.currentTimeMillis
+
+ // eliminate all empty diagrams
+ if (result.isDefined && result.get.edges.forall(_._2.isEmpty))
+ None
+ else
+ result
+ }
+
+}