package org.jetbrains.dokka import java.util.* enum class NodeKind { Unknown, Package, Class, Interface, Enum, AnnotationClass, Exception, EnumItem, Object, TypeAlias, Constructor, Function, Property, Field, CompanionObjectProperty, CompanionObjectFunction, Parameter, Receiver, TypeParameter, Type, Supertype, UpperBound, LowerBound, TypeAliasUnderlyingType, Modifier, NullabilityModifier, Module, ExternalClass, Annotation, Value, SourceUrl, SourcePosition, Signature, ExternalLink, QualifiedName, Platform, AllTypes, /** * A note which is rendered once on a page documenting a group of overloaded functions. * Needs to be generated equally on all overloads. */ OverloadGroupNote, Attribute, AttributeRef, ApiLevel, SdkExtSince, DeprecatedLevel, ArtifactId, GroupNode; companion object { val classLike = setOf(Class, Interface, Enum, AnnotationClass, Exception, Object, TypeAlias) val memberLike = setOf(Function, Property, Field, Constructor, CompanionObjectFunction, CompanionObjectProperty, EnumItem, Attribute) } } open class DocumentationNode(val name: String, content: Content, val kind: NodeKind) { private val references = LinkedHashSet() var content: Content = content private set val summary: ContentNode get() = content.summary val owner: DocumentationNode? get() = references(RefKind.Owner).singleOrNull()?.to val details: List get() = references(RefKind.Detail).map { it.to } val members: List get() = references(RefKind.Member).map { it.to }.sortedBy { it.name } val inheritedMembers: List get() = references(RefKind.InheritedMember).map { it.to } val allInheritedMembers: List get() = recursiveInheritedMembers().sortedBy { it.name } val inheritedCompanionObjectMembers: List get() = references(RefKind.InheritedCompanionObjectMember).map { it.to } val extensions: List get() = references(RefKind.Extension).map { it.to } val inheritors: List get() = references(RefKind.Inheritor).map { it.to } val overrides: List get() = references(RefKind.Override).map { it.to } val links: List get() = references(RefKind.Link).map { it.to } val hiddenLinks: List get() = references(RefKind.HiddenLink).map { it.to } val annotations: List get() = references(RefKind.Annotation).map { it.to } val deprecation: DocumentationNode? get() = references(RefKind.Deprecation).map { it.to }.firstOrNull() val platforms: List get() = references(RefKind.Platform).map { it.to.name } val externalType: DocumentationNode? get() = references(RefKind.ExternalType).map { it.to }.firstOrNull() val apiLevel: DocumentationNode get() = detailOrNull(NodeKind.ApiLevel) ?: DocumentationNode("", Content.Empty, NodeKind.ApiLevel) val sdkExtSince: DocumentationNode get() = detailOrNull(NodeKind.SdkExtSince) ?: DocumentationNode("", Content.Empty, NodeKind.SdkExtSince) val deprecatedLevel: DocumentationNode get() = detailOrNull(NodeKind.DeprecatedLevel) ?: DocumentationNode("", Content.Empty, NodeKind.DeprecatedLevel) val artifactId: DocumentationNode get() = detailOrNull(NodeKind.ArtifactId) ?: DocumentationNode("", Content.Empty, NodeKind.ArtifactId) val attributes: List get() = details(NodeKind.Attribute).sortedBy { it.attributeRef!!.name } val attributeRef: DocumentationNode? get() = references(RefKind.AttributeRef).map { it.to }.firstOrNull() val relatedAttributes: List get() = hiddenLinks(NodeKind.Attribute) val supertypes: List get() = details(NodeKind.Supertype) val signatureName = detailOrNull(NodeKind.Signature)?.name val prettyName : String get() = when(kind) { NodeKind.Constructor -> owner!!.name else -> name } val superclassType: DocumentationNode? get() = when (kind) { NodeKind.Supertype -> { (links + listOfNotNull(externalType)).firstOrNull { it.kind in NodeKind.classLike }?.superclassType } NodeKind.Interface -> null in NodeKind.classLike -> supertypes.firstOrNull { (it.links + listOfNotNull(it.externalType)).any { it.isSuperclassFor(this) } } else -> null } val superclassTypeSequence: Sequence get() = generateSequence(superclassType) { it.superclassType } // TODO: Should we allow node mutation? Model merge will copy by ref, so references are transparent, which could nice fun addReferenceTo(to: DocumentationNode, kind: RefKind) { references.add(DocumentationReference(this, to, kind)) } fun dropReferences(predicate: (DocumentationReference) -> Boolean) { references.removeAll(predicate) } fun addAllReferencesFrom(other: DocumentationNode) { references.addAll(other.references) } fun updateContent(body: MutableContent.() -> Unit) { if (content !is MutableContent) { content = MutableContent() } (content as MutableContent).body() } fun details(kind: NodeKind): List = details.filter { it.kind == kind } fun members(kind: NodeKind): List = members.filter { it.kind == kind } fun hiddenLinks(kind: NodeKind): List = hiddenLinks.filter { it.kind == kind } fun inheritedMembers(kind: NodeKind): List = inheritedMembers.filter { it.kind == kind } fun inheritedCompanionObjectMembers(kind: NodeKind): List = inheritedCompanionObjectMembers.filter { it.kind == kind } fun links(kind: NodeKind): List = links.filter { it.kind == kind } fun detail(kind: NodeKind): DocumentationNode = details.filter { it.kind == kind }.single() fun detailOrNull(kind: NodeKind): DocumentationNode? = details.filter { it.kind == kind }.singleOrNull() fun member(kind: NodeKind): DocumentationNode = members.filter { it.kind == kind }.single() fun link(kind: NodeKind): DocumentationNode = links.filter { it.kind == kind }.single() fun references(kind: RefKind): List = references.filter { it.kind == kind } fun allReferences(): Set = references override fun toString(): String { return "$kind:$name" } } class DocumentationModule(name: String, content: Content = Content.Empty) : DocumentationNode(name, content, NodeKind.Module) { val nodeRefGraph = NodeReferenceGraph() } val DocumentationNode.path: List get() { val parent = owner ?: return listOf(this) return parent.path + this } fun findOrCreatePackageNode(module: DocumentationNode?, packageName: String, packageContent: Map, refGraph: NodeReferenceGraph): DocumentationNode { val existingNode = refGraph.lookup(packageName) if (existingNode != null) { return existingNode } val newNode = DocumentationNode(packageName, packageContent.getOrElse(packageName) { Content.Empty }, NodeKind.Package) refGraph.register(packageName, newNode) module?.append(newNode, RefKind.Member) return newNode } fun DocumentationNode.append(child: DocumentationNode, kind: RefKind) { addReferenceTo(child, kind) when (kind) { RefKind.Detail -> child.addReferenceTo(this, RefKind.Owner) RefKind.Member -> child.addReferenceTo(this, RefKind.Owner) RefKind.Owner -> child.addReferenceTo(this, RefKind.Member) else -> { /* Do not add any links back for other types */ } } } fun DocumentationNode.appendTextNode(text: String, kind: NodeKind, refKind: RefKind = RefKind.Detail) { append(DocumentationNode(text, Content.Empty, kind), refKind) } fun DocumentationNode.qualifiedName(): String { if (kind == NodeKind.Type) { return qualifiedNameFromType() } else if (kind == NodeKind.Package) { return name } return path.drop(1).map { it.name }.filter { it.length > 0 }.joinToString(".") } fun DocumentationNode.simpleName() = name.substringAfterLast('.') private fun DocumentationNode.recursiveInheritedMembers(): List { val allInheritedMembers = mutableListOf() recursiveInheritedMembers(allInheritedMembers) return allInheritedMembers } private fun DocumentationNode.recursiveInheritedMembers(allInheritedMembers: MutableList) { allInheritedMembers.addAll(inheritedMembers) inheritedMembers.groupBy { it.owner!! } .forEach { (node, _) -> node.recursiveInheritedMembers(allInheritedMembers) } } private fun DocumentationNode.isSuperclassFor(node: DocumentationNode): Boolean { return when(node.kind) { NodeKind.Object, NodeKind.Class, NodeKind.Enum -> kind == NodeKind.Class NodeKind.Exception -> kind == NodeKind.Class || kind == NodeKind.Exception else -> false } } fun DocumentationNode.classNodeNameWithOuterClass(): String { assert(kind in NodeKind.classLike) return path.dropWhile { it.kind !in NodeKind.classLike }.joinToString(separator = ".") { it.name } } fun DocumentationNode.deprecatedLevelMessage(): String { val kindName = when(kind) { NodeKind.Enum -> "enum" NodeKind.Interface -> "interface" NodeKind.AnnotationClass -> "annotation" NodeKind.Exception -> "exception" else -> "class" } return "This $kindName was deprecated in API level ${deprecatedLevel.name}." } fun DocumentationNode.convertDeprecationDetailsToChildren() { val toProcess = details.toMutableList() while (!toProcess.isEmpty()) { var child = toProcess.removeAt(0) if (child.details.isEmpty() && child.name != "") { updateContent { text(child.name.cleanForAppending()) } } else toProcess.addAll(0, child.details) } } /* * Removes extraneous quotation marks and adds a ". " to make appending children readable. */ fun String.cleanForAppending(): String { var result = this if (this.first() == this.last() && this.first() == '"') result = result.substring(1, result.length - 1) if (result[result.length - 2] != '.' && result.last() != ' ') result += ". " return result }