diff options
author | Paul Phillips <paulp@improving.org> | 2013-03-06 07:39:19 -0800 |
---|---|---|
committer | Adriaan Moors <adriaan.moors@typesafe.com> | 2013-03-09 11:59:07 -0800 |
commit | c6ca941ccc017a8869f4def717cfeb640f965077 (patch) | |
tree | e21038cc55d3a4231544d148913504a754eafdf8 /src/scaladoc/scala/tools/nsc/doc/model/diagram | |
parent | 9094822181c398b945b7f30ac1e2b05da9796f53 (diff) | |
download | scala-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')
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 + } + +} |