aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/kotlin/Model/DocumentationNode.kt
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/main/kotlin/Model/DocumentationNode.kt')
-rw-r--r--core/src/main/kotlin/Model/DocumentationNode.kt311
1 files changed, 311 insertions, 0 deletions
diff --git a/core/src/main/kotlin/Model/DocumentationNode.kt b/core/src/main/kotlin/Model/DocumentationNode.kt
new file mode 100644
index 000000000..7ac6d8f4a
--- /dev/null
+++ b/core/src/main/kotlin/Model/DocumentationNode.kt
@@ -0,0 +1,311 @@
+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<DocumentationReference>()
+
+ var content: Content = content
+ private set
+
+ val summary: ContentNode get() = content.summary
+
+ val owner: DocumentationNode?
+ get() = references(RefKind.Owner).singleOrNull()?.to
+ val details: List<DocumentationNode>
+ get() = references(RefKind.Detail).map { it.to }
+ val members: List<DocumentationNode>
+ get() = references(RefKind.Member).map { it.to }.sortedBy { it.name }
+ val inheritedMembers: List<DocumentationNode>
+ get() = references(RefKind.InheritedMember).map { it.to }
+ val allInheritedMembers: List<DocumentationNode>
+ get() = recursiveInheritedMembers().sortedBy { it.name }
+ val inheritedCompanionObjectMembers: List<DocumentationNode>
+ get() = references(RefKind.InheritedCompanionObjectMember).map { it.to }
+ val extensions: List<DocumentationNode>
+ get() = references(RefKind.Extension).map { it.to }
+ val inheritors: List<DocumentationNode>
+ get() = references(RefKind.Inheritor).map { it.to }
+ val overrides: List<DocumentationNode>
+ get() = references(RefKind.Override).map { it.to }
+ val links: List<DocumentationNode>
+ get() = references(RefKind.Link).map { it.to }
+ val hiddenLinks: List<DocumentationNode>
+ get() = references(RefKind.HiddenLink).map { it.to }
+ val annotations: List<DocumentationNode>
+ get() = references(RefKind.Annotation).map { it.to }
+ val deprecation: DocumentationNode?
+ get() = references(RefKind.Deprecation).map { it.to }.firstOrNull()
+ val platforms: List<String>
+ 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<DocumentationNode>
+ get() = details(NodeKind.Attribute).sortedBy { it.attributeRef!!.name }
+ val attributeRef: DocumentationNode?
+ get() = references(RefKind.AttributeRef).map { it.to }.firstOrNull()
+ val relatedAttributes: List<DocumentationNode>
+ get() = hiddenLinks(NodeKind.Attribute)
+ val supertypes: List<DocumentationNode>
+ 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<DocumentationNode>
+ 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<DocumentationNode> = details.filter { it.kind == kind }
+ fun members(kind: NodeKind): List<DocumentationNode> = members.filter { it.kind == kind }
+ fun hiddenLinks(kind: NodeKind): List<DocumentationNode> = hiddenLinks.filter { it.kind == kind }
+ fun inheritedMembers(kind: NodeKind): List<DocumentationNode> = inheritedMembers.filter { it.kind == kind }
+ fun inheritedCompanionObjectMembers(kind: NodeKind): List<DocumentationNode> = inheritedCompanionObjectMembers.filter { it.kind == kind }
+ fun links(kind: NodeKind): List<DocumentationNode> = 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<DocumentationReference> = references.filter { it.kind == kind }
+ fun allReferences(): Set<DocumentationReference> = 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<DocumentationNode>
+ get() {
+ val parent = owner ?: return listOf(this)
+ return parent.path + this
+ }
+
+fun findOrCreatePackageNode(module: DocumentationNode?, packageName: String, packageContent: Map<String, Content>, 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<DocumentationNode> {
+ val allInheritedMembers = mutableListOf<DocumentationNode>()
+ recursiveInheritedMembers(allInheritedMembers)
+ return allInheritedMembers
+}
+
+private fun DocumentationNode.recursiveInheritedMembers(allInheritedMembers: MutableList<DocumentationNode>) {
+ 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
+} \ No newline at end of file