summaryrefslogtreecommitdiff
path: root/src/scaladoc/scala/tools/nsc/doc/model/diagram/Diagram.scala
blob: e15963bda924a452fb41c320542938ec0f768e7f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package scala.tools.nsc.doc
package model
package diagram

import model._

/**
 *  The diagram base classes
 *
 *  @author Damien Obrist
 *  @author Vlad Ureche
 */
sealed 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
}

sealed 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 template
 */
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
}