summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala
blob: d80999e1490807cce49c412a2fd3ac15baa9d55d (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
138
139
140
141
142
143
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 isPackageDiagram = false
  def isClassDiagram = false
  def depthInfo: DepthInfo
}

case class PackageDiagram(nodes:List[/*Class*/Node], edges:List[(Node, List[Node])]) extends Diagram {
  override def isPackageDiagram = true
  lazy val depthInfo = new PackageDiagramDepth(this)
}

/** A class diagram */
case class ClassDiagram(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 isClassDiagram = true
  lazy val depthInfo = new DepthInfo {
    def maxDepth = 3
    def nodeDepth(node: Node) =
      if (node == thisNode) 1
      else if (superClasses.contains(node)) 0
      else if (subClasses.contains(node)) 2
      else if (incomingImplicits.contains(node) || outgoingImplicits.contains(node)) 1
      else -1
  }
}

trait DepthInfo {
  /** Gives the maximum depth */
  def maxDepth: Int
  /** Gives the depth of any node in the diagram or -1 if the node is not in the diagram */
  def nodeDepth(node: Node): 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 isOtherNode = !(isClassNode || isTraitNode || isObjectNode)
  def isImplicitNode = false
  def isOutsideNode = false
}

// 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 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]) extends Node { override def isThisNode = true }

/** The usual node */
case class NormalNode(tpe: TypeEntity, tpl: Option[TemplateEntity]) 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]) 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]) extends Node { override def isOutsideNode = true }


// Computing and offering node depth information
class PackageDiagramDepth(pack: PackageDiagram) 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
  def nodeDepth(node: Node) = _nodeDepth.getOrElse(node, -1)
}