aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt
diff options
context:
space:
mode:
authorTiem Song <tiem@google.com>2019-07-03 14:16:39 -0700
committerTiem Song <tiem@google.com>2019-07-10 10:52:51 -0700
commite1dd51247a902084dc8cc3e0601b4e78cc1d8fc1 (patch)
tree0b413d06d9c733301c537e6ee7a8dc8819457369 /core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt
parentf1827c37b50434598d91e7df414f985343e2e740 (diff)
downloaddokka-e1dd51247a902084dc8cc3e0601b4e78cc1d8fc1.tar.gz
Adding Dokka source to AOSP
This is taken from commit 4ff3d4153ca344398bffcdaaa28f1a1f6e76f6ad from https://github.com/google/dokka, which is Google's customized fork of Dokka (https://github.com/Kotlin/dokka). Bug: 135767980 Test: ./gradlew :core:cleanTest :core:test Test: ./gradlew :runners:gradle-integration-tests:clean :runners:gradle-integration-tests:test Change-Id: I332d0b522706e353c3837f6308aac1a8340d71d3
Diffstat (limited to 'core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt')
-rw-r--r--core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt1155
1 files changed, 1155 insertions, 0 deletions
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt
new file mode 100644
index 000000000..9ffa7a250
--- /dev/null
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt
@@ -0,0 +1,1155 @@
+package org.jetbrains.dokka.Formats
+
+import com.google.common.base.Throwables
+import kotlinx.html.*
+import kotlinx.html.Entities.nbsp
+import kotlinx.html.stream.appendHTML
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.LanguageService.RenderMode.FULL
+import org.jetbrains.dokka.LanguageService.RenderMode.SUMMARY
+import org.jetbrains.dokka.NodeKind.Companion.classLike
+import java.net.URI
+import javax.inject.Inject
+
+
+open class JavaLayoutHtmlFormatOutputBuilder(
+ val output: Appendable,
+ val languageService: LanguageService,
+ val uriProvider: JavaLayoutHtmlUriProvider,
+ val templateService: JavaLayoutHtmlTemplateService,
+ val logger: DokkaLogger,
+ val uri: URI
+) {
+
+ val htmlConsumer = output.appendHTML()
+
+
+ private fun FlowContent.hN(
+ level: Int,
+ classes: String? = null,
+ block: CommonAttributeGroupFacadeFlowHeadingPhrasingContent.() -> Unit
+ ) {
+ when (level) {
+ 1 -> h1(classes, block)
+ 2 -> h2(classes, block)
+ 3 -> h3(classes, block)
+ 4 -> h4(classes, block)
+ 5 -> h5(classes, block)
+ 6 -> h6(classes, block)
+ }
+ }
+
+ protected open fun FlowContent.metaMarkup(content: List<ContentNode>, contextUri: URI = uri) =
+ contentNodesToMarkup(content, contextUri)
+
+ protected fun FlowContent.nodeContent(node: DocumentationNode, uriNode: DocumentationNode) =
+ contentNodeToMarkup(node.content, uriProvider.mainUriOrWarn(uriNode) ?: uri)
+
+ protected fun FlowContent.nodeContent(node: DocumentationNode) =
+ nodeContent(node, node)
+
+ protected fun FlowContent.contentNodesToMarkup(content: List<ContentNode>, contextUri: URI = uri): Unit =
+ content.forEach { contentNodeToMarkup(it, contextUri) }
+
+ private fun FlowContent.contentNodeToMarkup(content: ContentNode, contextUri: URI) {
+ when (content) {
+ is ContentText -> +content.text
+ is ContentSymbol -> span("symbol") { +content.text }
+ is ContentKeyword -> span("keyword") { +content.text }
+ is ContentIdentifier -> span("identifier") {
+ content.signature?.let { id = it }
+ +content.text
+ }
+
+ is ContentHeading -> hN(level = content.level) { contentNodesToMarkup(content.children, contextUri) }
+
+ is ContentEntity -> +content.text
+
+ is ContentStrong -> strong { contentNodesToMarkup(content.children, contextUri) }
+ is ContentStrikethrough -> del { contentNodesToMarkup(content.children, contextUri) }
+ is ContentEmphasis -> em { contentNodesToMarkup(content.children, contextUri) }
+
+ is ContentOrderedList -> ol { contentNodesToMarkup(content.children, contextUri) }
+ is ContentUnorderedList -> ul { contentNodesToMarkup(content.children, contextUri) }
+ is ContentListItem -> consumer.li {
+ (content.children.singleOrNull() as? ContentParagraph)
+ ?.let { paragraph -> contentNodesToMarkup(paragraph.children, contextUri) }
+ ?: contentNodesToMarkup(content.children, contextUri)
+ }
+
+ is ContentDescriptionList -> dl { contentNodesToMarkup(content.children, contextUri) }
+ is ContentDescriptionTerm -> consumer.dt {
+ (content.children.singleOrNull() as? ContentParagraph)
+ ?.let { paragraph -> this@contentNodeToMarkup.contentNodesToMarkup(paragraph.children, contextUri) }
+ ?: this@contentNodeToMarkup.contentNodesToMarkup(content.children, contextUri)
+ }
+ is ContentDescriptionDefinition -> consumer.dd {
+ (content.children.singleOrNull() as? ContentParagraph)
+ ?.let { paragraph -> contentNodesToMarkup(paragraph.children, contextUri) }
+ ?: contentNodesToMarkup(content.children, contextUri)
+ }
+
+ is ContentTable -> table { contentNodesToMarkup(content.children, contextUri) }
+ is ContentTableBody -> consumer.tbody { this@contentNodeToMarkup.contentNodesToMarkup(content.children, contextUri) }
+ is ContentTableRow -> consumer.tr { this@contentNodeToMarkup.contentNodesToMarkup(content.children, contextUri) }
+ is ContentTableHeader -> consumer.th {
+ content.colspan?.let {
+ if (it.isNotBlank()) {
+ attributes["colspan"] = content.colspan
+ }
+ }
+ content.rowspan?.let {
+ if (it.isNotBlank()) {
+ attributes["rowspan"] = content.rowspan
+ }
+ }
+ (content.children.singleOrNull() as? ContentParagraph)
+ ?.let { paragraph -> this@contentNodeToMarkup.contentNodesToMarkup(paragraph.children, contextUri) }
+ ?: this@contentNodeToMarkup.contentNodesToMarkup(content.children, contextUri)
+ }
+ is ContentTableCell -> consumer.td {
+ content.colspan?.let {
+ if (it.isNotBlank()) {
+ attributes["colspan"] = content.colspan
+ }
+ }
+ content.rowspan?.let {
+ if (it.isNotBlank()) {
+ attributes["rowspan"] = content.rowspan
+ }
+ }
+ (content.children.singleOrNull() as? ContentParagraph)
+ ?.let { paragraph -> contentNodesToMarkup(paragraph.children, contextUri) }
+ ?: contentNodesToMarkup(content.children, contextUri)
+ }
+
+ is ContentSpecialReference -> aside(classes = "note") {
+ contentNodesToMarkup(content.children, contextUri)
+ }
+
+ is ContentCode -> contentInlineCode(content)
+ is ContentBlockSampleCode -> contentBlockSampleCode(content)
+ is ContentBlockCode -> contentBlockCode(content)
+
+ ContentNonBreakingSpace -> +nbsp
+ ContentSoftLineBreak, ContentIndentedSoftLineBreak -> {
+ }
+ ContentHardLineBreak -> br
+
+ is ContentParagraph -> p { contentNodesToMarkup(content.children, contextUri) }
+
+ is NodeRenderContent -> renderedSignature(content.node, mode = content.mode)
+ is ContentNodeLink -> {
+ fun FlowContent.body() = contentNodesToMarkup(content.children, contextUri)
+
+ when (content.node?.kind) {
+ NodeKind.TypeParameter -> body()
+ else -> a(href = content.node, block = FlowContent::body)
+ }
+ }
+ is ContentBookmark -> a {
+ id = content.name
+ contentNodesToMarkup(content.children, contextUri)
+ }
+ is ContentExternalLink -> contentExternalLink(content)
+ is ContentLocalLink -> a(href = contextUri.resolve(content.href).relativeTo(uri).toString()) {
+ contentNodesToMarkup(content.children, contextUri)
+ }
+ is ContentSection -> {
+ }
+ is ContentBlock -> contentNodesToMarkup(content.children, contextUri)
+ }
+ }
+
+ protected open fun FlowContent.contentInlineCode(content: ContentCode) {
+ code { contentNodesToMarkup(content.children) }
+ }
+
+ protected open fun FlowContent.contentBlockSampleCode(content: ContentBlockSampleCode) {
+ pre {
+ code {
+ attributes["data-language"] = content.language
+ contentNodesToMarkup(content.importsBlock.children)
+ +"\n\n"
+ contentNodesToMarkup(content.children)
+ }
+ }
+ }
+
+ protected open fun FlowContent.contentBlockCode(content: ContentBlockCode) {
+ pre {
+ code {
+ attributes["data-language"] = content.language
+ contentNodesToMarkup(content.children)
+ }
+ }
+ }
+
+ protected open fun FlowContent.contentExternalLink(content: ContentExternalLink) {
+ a(href = content.href) { contentNodesToMarkup(content.children) }
+ }
+
+ protected open fun <T> FlowContent.summaryNodeGroup(
+ nodes: Iterable<T>,
+ header: String,
+ headerAsRow: Boolean = true,
+ row: TBODY.(T) -> Unit
+ ) {
+ if (nodes.none()) return
+ if (!headerAsRow) {
+ h2 { +header }
+ }
+ table {
+ tbody {
+ if (headerAsRow) {
+ developerHeading(header)
+ }
+ nodes.forEach { node ->
+ row(node)
+ }
+ }
+ }
+ }
+
+
+ protected open fun summary(node: DocumentationNode) = node.summary
+
+ protected open fun TBODY.classLikeRow(node: DocumentationNode) = tr {
+ td { a(href = uriProvider.linkTo(node, uri)) { +node.simpleName() } }
+ td { nodeSummary(node) }
+ }
+
+ protected fun FlowContent.modifiers(node: DocumentationNode) {
+ for (modifier in node.details(NodeKind.Modifier)) {
+ renderedSignature(modifier, SUMMARY)
+ }
+ }
+
+ protected fun FlowContent.shortFunctionParametersList(func: DocumentationNode) {
+ val params = func.details(NodeKind.Parameter)
+ .map { languageService.render(it, FULL) }
+ .run {
+ drop(1).fold(listOfNotNull(firstOrNull())) { acc, node ->
+ acc + ContentText(", ") + node
+ }
+ }
+ metaMarkup(listOf(ContentText("(")) + params + listOf(ContentText(")")))
+ }
+
+
+ protected open fun TBODY.functionLikeSummaryRow(node: DocumentationNode) = tr {
+ if (node.kind != NodeKind.Constructor) {
+ td {
+ modifiers(node)
+ renderedSignature(node.detail(NodeKind.Type), SUMMARY)
+ }
+ }
+ td {
+ div {
+ code {
+ val receiver = node.detailOrNull(NodeKind.Receiver)
+ if (receiver != null) {
+ renderedSignature(receiver.detail(NodeKind.Type), SUMMARY)
+ +"."
+ }
+ a(href = node) { +node.name }
+ shortFunctionParametersList(node)
+ }
+ }
+
+ nodeSummary(node)
+ }
+ }
+
+ protected open fun TBODY.propertyLikeSummaryRow(node: DocumentationNode, showSignature: Boolean = true) = tr {
+ if (showSignature) {
+ td {
+ modifiers(node)
+ renderedSignature(node.detail(NodeKind.Type), SUMMARY)
+ }
+ }
+ td {
+ div {
+ code {
+ a(href = node) { +node.name }
+ }
+ }
+
+ nodeSummary(node)
+ }
+ }
+
+ protected open fun TBODY.nestedClassSummaryRow(node: DocumentationNode) = tr {
+ td {
+ modifiers(node)
+ }
+ td {
+ div {
+ code {
+ a(href = node) { +node.name }
+ }
+ }
+
+ nodeSummary(node)
+ }
+ }
+
+ protected fun HtmlBlockTag.nodeSummary(node: DocumentationNode, uriNode: DocumentationNode) {
+ contentNodeToMarkup(summary(node), uriProvider.mainUriOrWarn(uriNode) ?: uri)
+ }
+
+ protected fun HtmlBlockTag.nodeSummary(node: DocumentationNode) {
+ nodeSummary(node, node)
+ }
+
+ protected open fun TBODY.inheritRow(
+ entry: Map.Entry<DocumentationNode, List<DocumentationNode>>,
+ summaryRow: TBODY.(DocumentationNode) -> Unit
+ ) = tr {
+ td {
+ val (from, nodes) = entry
+ +"From class "
+ a(href = from.owner!!) { +from.qualifiedName() }
+ table {
+ tbody {
+ for (node in nodes) {
+ summaryRow(node)
+ }
+ }
+ }
+ }
+ }
+
+ protected open fun TBODY.groupedRow(
+ entry: Map.Entry<DocumentationNode, List<DocumentationNode>>,
+ groupHeader: HtmlBlockTag.(DocumentationNode) -> Unit,
+ summaryRow: TBODY.(DocumentationNode) -> Unit
+ ) = tr {
+ td {
+ val (from, nodes) = entry
+ groupHeader(from)
+ table {
+ tbody {
+ for (node in nodes) {
+ summaryRow(node)
+ }
+ }
+ }
+ }
+ }
+
+ protected open fun TBODY.extensionRow(
+ entry: Map.Entry<DocumentationNode, List<DocumentationNode>>,
+ summaryRow: TBODY.(DocumentationNode) -> Unit
+ ) = groupedRow(entry, { from ->
+ +"From "
+ a(href = from) { +from.qualifiedName() }
+ }, summaryRow)
+
+
+ protected open fun TBODY.extensionByReceiverRow(
+ entry: Map.Entry<DocumentationNode, List<DocumentationNode>>,
+ summaryRow: TBODY.(DocumentationNode) -> Unit
+ ) = groupedRow(entry, { from ->
+ +"For "
+ a(href = from) { +from.name }
+ }, summaryRow)
+
+ protected open fun FlowOrInteractiveOrPhrasingContent.a(href: DocumentationNode?, classes: String? = null, block: HtmlBlockInlineTag.() -> Unit) {
+ if (href == null) {
+ return a(href = "#", classes = classes, block = block)
+ }
+
+ val hrefText = try {
+ href.name.takeIf { href.kind == NodeKind.ExternalLink }
+ ?: href.links.firstOrNull { it.kind == NodeKind.ExternalLink }?.name
+ ?: "#".takeIf { href.kind == NodeKind.ExternalClass } // When external class unresolved
+ ?: uriProvider.linkTo(href, uri)
+ } catch (e: Exception) {
+ val owners = generateSequence(href) { it.owner }.toList().reversed()
+ logger.warn("Exception while resolving link to ${owners.joinToString(separator = " ")}\n"
+ + Throwables.getStackTraceAsString(e))
+ "#"
+ }
+
+ a(href = hrefText, classes = classes, block = block)
+ }
+
+ protected open fun FlowContent.renderedSignature(
+ node: DocumentationNode,
+ mode: LanguageService.RenderMode = SUMMARY
+ ) {
+ contentNodeToMarkup(languageService.render(node, mode), uri)
+ }
+
+ protected open fun generatePackage(page: Page.PackagePage) = templateService.composePage(
+ page,
+ htmlConsumer,
+ headContent = {
+
+ },
+ bodyContent = {
+ h1 { +page.node.name }
+ nodeContent(page.node)
+ summaryNodeGroup(page.classes, "Classes", headerAsRow = false) { classLikeRow(it) }
+ summaryNodeGroup(page.exceptions, "Exceptions", headerAsRow = false) { classLikeRow(it) }
+ summaryNodeGroup(page.typeAliases, "Type-aliases", headerAsRow = false) { classLikeRow(it) }
+ summaryNodeGroup(page.annotations, "Annotations", headerAsRow = false) { classLikeRow(it) }
+ summaryNodeGroup(page.enums, "Enums", headerAsRow = false) { classLikeRow(it) }
+
+ summaryNodeGroup(
+ page.constants,
+ "Top-level constants summary",
+ headerAsRow = false
+ ) {
+ propertyLikeSummaryRow(it)
+ }
+
+ summaryNodeGroup(
+ page.functions,
+ "Top-level functions summary",
+ headerAsRow = false
+ ) {
+ functionLikeSummaryRow(it)
+ }
+
+ summaryNodeGroup(
+ page.properties,
+ "Top-level properties summary",
+ headerAsRow = false
+ ) {
+ propertyLikeSummaryRow(it)
+ }
+
+ summaryNodeGroup(
+ page.extensionFunctions.entries,
+ "Extension functions summary",
+ headerAsRow = false
+ ) {
+ extensionByReceiverRow(it) {
+ functionLikeSummaryRow(it)
+ }
+ }
+
+ summaryNodeGroup(
+ page.extensionProperties.entries,
+ "Extension properties summary",
+ headerAsRow = false
+ ) {
+ extensionByReceiverRow(it) {
+ functionLikeSummaryRow(it)
+ }
+ }
+
+ fullMemberDocs(page.constants, "Top-level constants")
+ fullMemberDocs(page.functions, "Top-level functions")
+ fullMemberDocs(page.properties, "Top-level properties")
+ fullMemberDocs(page.extensionFunctions.values.flatten(), "Extension functions")
+ fullMemberDocs(page.extensionProperties.values.flatten(), "Extension properties")
+ }
+ )
+
+ protected fun FlowContent.qualifiedTypeReference(node: DocumentationNode) {
+ if (node.kind in classLike) {
+ a(href = node) { +node.qualifiedName() }
+ return
+ }
+
+ val targetLink = node.links.singleOrNull()
+
+ if (targetLink?.kind == NodeKind.TypeParameter) {
+ +node.name
+ return
+ }
+
+ a(href = targetLink) {
+ +node.qualifiedNameFromType()
+ }
+ val typeParameters = node.details(NodeKind.Type)
+ if (typeParameters.isNotEmpty()) {
+ +"<"
+ typeParameters.forEach {
+ if (it != typeParameters.first()) {
+ +", "
+ }
+ qualifiedTypeReference(it)
+ }
+ +">"
+ }
+ }
+
+ protected open fun FlowContent.classHierarchy(superclasses: List<DocumentationNode>) {
+ table {
+ superclasses.forEach {
+ tr {
+ if (it != superclasses.first()) {
+ td {
+ +"   ↳"
+ }
+ }
+ td {
+ qualifiedTypeReference(it)
+ }
+ }
+ }
+ }
+ }
+
+ protected open fun FlowContent.subclasses(inheritors: List<DocumentationNode>, direct: Boolean) {
+ if (inheritors.isEmpty()) return
+ div {
+ table {
+ thead {
+ tr {
+ td {
+ if (direct)
+ +"Known Direct Subclasses"
+ else
+ +"Known Indirect Subclasses"
+ }
+ }
+ }
+ tbody {
+ inheritors.forEach { inheritor ->
+ tr {
+ td {
+ a(href = inheritor) { +inheritor.classNodeNameWithOuterClass() }
+ }
+ td {
+ nodeSummary(inheritor)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ protected open fun FlowContent.classLikeSummaries(page: Page.ClassPage) = with(page) {
+ summaryNodeGroup(
+ nestedClasses,
+ "Nested classes",
+ headerAsRow = true
+ ) {
+ nestedClassSummaryRow(it)
+ }
+
+ summaryNodeGroup(enumValues, "Enum values") {
+ propertyLikeSummaryRow(it)
+ }
+
+ summaryNodeGroup(constants, "Constants") { propertyLikeSummaryRow(it) }
+
+ constructors.forEach { (visibility, group) ->
+ summaryNodeGroup(
+ group,
+ "${visibility.capitalize()} constructors",
+ headerAsRow = true
+ ) {
+ functionLikeSummaryRow(it)
+ }
+ }
+
+ functions.forEach { (visibility, group) ->
+ summaryNodeGroup(
+ group,
+ "${visibility.capitalize()} functions",
+ headerAsRow = true
+ ) {
+ functionLikeSummaryRow(it)
+ }
+ }
+
+ summaryNodeGroup(
+ companionFunctions,
+ "Companion functions",
+ headerAsRow = true
+ ) {
+ functionLikeSummaryRow(it)
+ }
+ summaryNodeGroup(
+ inheritedFunctionsByReceiver.entries,
+ "Inherited functions",
+ headerAsRow = true
+ ) {
+ inheritRow(it) {
+ functionLikeSummaryRow(it)
+ }
+ }
+ summaryNodeGroup(
+ extensionFunctions.entries,
+ "Extension functions",
+ headerAsRow = true
+ ) {
+ extensionRow(it) {
+ functionLikeSummaryRow(it)
+ }
+ }
+ summaryNodeGroup(
+ inheritedExtensionFunctions.entries,
+ "Inherited extension functions",
+ headerAsRow = true
+ ) {
+ extensionRow(it) {
+ functionLikeSummaryRow(it)
+ }
+ }
+
+
+ summaryNodeGroup(properties, "Properties", headerAsRow = true) { propertyLikeSummaryRow(it) }
+ summaryNodeGroup(
+ companionProperties,
+ "Companion properties",
+ headerAsRow = true
+ ) {
+ propertyLikeSummaryRow(it)
+ }
+
+ summaryNodeGroup(
+ inheritedPropertiesByReceiver.entries,
+ "Inherited properties",
+ headerAsRow = true
+ ) {
+ inheritRow(it) {
+ propertyLikeSummaryRow(it)
+ }
+ }
+ summaryNodeGroup(
+ extensionProperties.entries,
+ "Extension properties",
+ headerAsRow = true
+ ) {
+ extensionRow(it) {
+ propertyLikeSummaryRow(it)
+ }
+ }
+ summaryNodeGroup(
+ inheritedExtensionProperties.entries,
+ "Inherited extension properties",
+ headerAsRow = true
+ ) {
+ extensionRow(it) {
+ propertyLikeSummaryRow(it)
+ }
+ }
+ }
+
+ protected open fun FlowContent.classLikeFullMemberDocs(page: Page.ClassPage) = with(page) {
+ fullMemberDocs(enumValues, "Enum values")
+ fullMemberDocs(constants, "Constants")
+
+ constructors.forEach { (visibility, group) ->
+ fullMemberDocs(group, "${visibility.capitalize()} constructors")
+ }
+
+ functions.forEach { (visibility, group) ->
+ fullMemberDocs(group, "${visibility.capitalize()} methods")
+ }
+
+ fullMemberDocs(properties, "Properties")
+ if (!hasMeaningfulCompanion) {
+ fullMemberDocs(companionFunctions, "Companion functions")
+ fullMemberDocs(companionProperties, "Companion properties")
+ }
+ }
+
+ protected open fun generateClassLike(page: Page.ClassPage) = templateService.composePage(
+ page,
+ htmlConsumer,
+ headContent = {
+
+ },
+ bodyContent = {
+ val node = page.node
+ with(page) {
+
+ div {
+ id = "api-info-block"
+ apiAndDeprecatedVersions(node)
+ }
+
+ if (node.artifactId.name.isNotEmpty()) {
+ div(classes = "api-level") { br { +"belongs to Maven artifact ${node.artifactId}" } }
+ }
+ h1 { +node.name }
+ pre { renderedSignature(node, FULL) }
+ classHierarchy(page.superclasses)
+
+ subclasses(page.directInheritors, true)
+ subclasses(page.indirectInheritors, false)
+
+ deprecatedClassCallOut(node)
+ nodeContent(node)
+
+ h2 { +"Summary" }
+ classLikeSummaries(page)
+ classLikeFullMemberDocs(page)
+ }
+ }
+ )
+
+ protected open fun FlowContent.classIndexSummary(node: DocumentationNode) {
+ nodeContent(node)
+ }
+
+ protected open fun generateClassIndex(page: Page.ClassIndex) = templateService.composePage(
+ page,
+ htmlConsumer,
+ headContent = {
+
+ },
+ bodyContent = {
+ h1 { +"Class Index" }
+
+
+ ul {
+ page.classesByFirstLetter.forEach { (letter) ->
+ li { a(href = "#letter_$letter") { +letter } }
+ }
+ }
+
+ page.classesByFirstLetter.forEach { (letter, classes) ->
+ h2 {
+ id = "letter_$letter"
+ +letter
+ }
+ table {
+ tbody {
+ for (node in classes) {
+ tr {
+ td {
+ a(href = uriProvider.linkTo(node, uri)) { +node.classNodeNameWithOuterClass() }
+ }
+ td {
+ if (!deprecatedIndexSummary(node)) {
+ classIndexSummary(node)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ )
+
+ protected open fun generatePackageIndex(page: Page.PackageIndex) = templateService.composePage(
+ page,
+ htmlConsumer,
+ headContent = {
+
+ },
+ bodyContent = {
+ h1 { +"Package Index" }
+ table {
+ tbody {
+ for (node in page.packages) {
+ tr {
+ td {
+ a(href = uriProvider.linkTo(node, uri)) { +node.name }
+ }
+ td {
+ nodeContent(node)
+ }
+ }
+ }
+ }
+ }
+ }
+ )
+
+ fun generatePage(page: Page) {
+ when (page) {
+ is Page.PackageIndex -> generatePackageIndex(page)
+ is Page.ClassIndex -> generateClassIndex(page)
+ is Page.ClassPage -> generateClassLike(page)
+ is Page.PackagePage -> generatePackage(page)
+ }
+ }
+
+ protected fun FlowContent.fullMemberDocs(
+ nodes: List<DocumentationNode>,
+ header: String
+ ) {
+ if (nodes.none()) return
+ h2 {
+ +header
+ }
+ for (node in nodes) {
+ fullMemberDocs(node)
+ }
+ }
+
+ protected open fun FlowContent.seeAlsoSection(links: List<List<ContentNode>>) {
+ p { b { +"See Also" } }
+ ul {
+ links.forEach { linkParts ->
+ li { code { metaMarkup(linkParts) } }
+ }
+ }
+ }
+
+ protected open fun FlowContent.regularSection(name: String, entries: List<ContentSection>) {
+ table {
+ thead {
+ tr {
+ th {
+ colSpan = "2"
+ +name
+ }
+ }
+ }
+ tbody {
+ entries.forEach {
+ tr {
+ if (it.subjectName != null) {
+ td { +it.subjectName }
+ }
+ td {
+ metaMarkup(it.children)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ protected open fun FlowContent.deprecationWarningToMarkup(
+ node: DocumentationNode,
+ prefix: Boolean = false,
+ emphasis: Boolean = true
+ ): Boolean {
+ val deprecated = formatDeprecationOrNull(node, prefix, emphasis)
+ deprecated?.let {
+ contentNodeToMarkup(deprecated, uriProvider.mainUri(node))
+ return true
+ }
+ return false
+ }
+
+ protected open fun FlowContent.deprecatedClassCallOut(node: DocumentationNode) {
+ val deprecatedLevelExists = node.deprecatedLevel.name.isNotEmpty()
+ if (deprecatedLevelExists) {
+ hr { }
+ aside(classes = "caution") {
+ strong { +node.deprecatedLevelMessage() }
+ deprecationWarningToMarkup(node, emphasis = false)
+ }
+ }
+ }
+
+ protected open fun FlowContent.deprecatedIndexSummary(node: DocumentationNode): Boolean {
+ val deprecatedLevelExists = node.deprecatedLevel.name.isNotEmpty()
+ if (deprecatedLevelExists) {
+ val em = ContentEmphasis()
+ em.append(ContentText(node.deprecatedLevelMessage()))
+ em.append(ContentText(" "))
+ for (child in node.deprecation?.content?.children ?: emptyList<ContentNode>()) {
+ em.append(child)
+ }
+ contentNodeToMarkup(em, uriProvider.mainUri(node))
+ return true
+ }
+ return false
+ }
+
+ protected open fun FlowContent.apiAndDeprecatedVersions(node: DocumentationNode) {
+ val apiLevelExists = node.apiLevel.name.isNotEmpty()
+ val deprecatedLevelExists = node.deprecatedLevel.name.isNotEmpty()
+ if (apiLevelExists || deprecatedLevelExists) {
+ div(classes = "api-level") {
+ if (apiLevelExists) {
+ +"Added in "
+ a(href = "https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels") {
+ +"API level ${node.apiLevel.name}"
+ }
+ if (deprecatedLevelExists) {
+ br
+ }
+ }
+ if (deprecatedLevelExists) {
+ +"Deprecated in "
+ a(href = "https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels") {
+ +"API level ${node.deprecatedLevel.name}"
+ }
+ }
+ }
+ }
+ }
+
+ protected open fun formatDeprecationOrNull(
+ node: DocumentationNode,
+ prefix: Boolean = false,
+ emphasis: Boolean = true): ContentNode? {
+ val deprecated = node.deprecation
+ deprecated?.let {
+ return ContentParagraph().apply {
+ if (prefix) {
+ append(ContentStrong().apply { text(
+ if (deprecated.content.children.size == 0) "Deprecated."
+ else "Deprecated: "
+ ) })
+ }
+ val em = if (emphasis) ContentEmphasis() else ContentBlock()
+ for (child in deprecated.content.children) {
+ em.append(child)
+ }
+ append(em)
+ }
+ }
+ return null
+ }
+
+ protected open fun FlowContent.section(name: String, sectionParts: List<ContentSection>) {
+ when (name) {
+ ContentTags.SeeAlso -> seeAlsoSection(sectionParts.map { it.children.flatMap { (it as? ContentParagraph)?.children ?: listOf(it) } })
+ else -> regularSection(name, sectionParts)
+ }
+ }
+
+ protected open fun FlowContent.sections(content: Content) {
+ val sectionsByTag = content.sections.groupByTo(mutableMapOf()) { it.tag }
+
+ val seeAlso = sectionsByTag.remove(ContentTags.SeeAlso)
+
+ for ((name, entries) in sectionsByTag) {
+ section(name, entries)
+ }
+
+ seeAlso?.let { section(ContentTags.SeeAlso, it) }
+ }
+
+ protected open fun FlowContent.fullMemberDocs(node: DocumentationNode, uriNode: DocumentationNode) {
+ div {
+ id = node.signatureForAnchor(logger)
+ h3 { +node.name }
+ pre { renderedSignature(node, FULL) }
+ deprecationWarningToMarkup(node, prefix = true)
+ nodeContent(node)
+ node.constantValue()?.let { value ->
+ pre {
+ +"Value: "
+ code { +value }
+ }
+ }
+
+ sections(node.content)
+ }
+ }
+
+ protected open fun FlowContent.fullMemberDocs(node: DocumentationNode) {
+ fullMemberDocs(node, node)
+ }
+
+ sealed class Page {
+ class PackageIndex(packages: List<DocumentationNode>) : Page() {
+ init {
+ assert(packages.all { it.kind == NodeKind.Package })
+ }
+
+ val packages = packages.sortedBy { it.name }
+ }
+
+ class ClassIndex(allTypesNode: DocumentationNode) : Page() {
+ init {
+ assert(allTypesNode.kind == NodeKind.AllTypes)
+ }
+
+ // Wide-collect all nested classes
+ val classes: List<DocumentationNode> =
+ generateSequence(listOf(allTypesNode)) { nodes ->
+ nodes
+ .flatMap { it.members.filter { it.kind in NodeKind.classLike } }
+ .takeUnless { it.isEmpty() }
+ }.drop(1)
+ .flatten()
+ .sortedBy { it.classNodeNameWithOuterClass().toLowerCase() }
+ .toList()
+
+
+ // Group all classes by it's first letter and sort
+ val classesByFirstLetter =
+ classes
+ .groupBy {
+ it.classNodeNameWithOuterClass().first().toString()
+ }
+ .entries
+ .sortedBy { (letter) ->
+ val x = letter.toLowerCase()
+ x
+ }
+ }
+
+ class ClassPage(val node: DocumentationNode) : Page() {
+
+ init {
+ assert(node.kind in NodeKind.classLike)
+ }
+
+ val superclasses = (sequenceOf(node) + node.superclassTypeSequence).toList().asReversed()
+
+ val enumValues = node.members(NodeKind.EnumItem).sortedBy { it.name }
+
+ val directInheritors: List<DocumentationNode>
+ val indirectInheritors: List<DocumentationNode>
+
+ init {
+ // Wide-collect all inheritors
+ val inheritors = generateSequence(node.inheritors) { inheritors ->
+ inheritors
+ .flatMap { it.inheritors }
+ .takeUnless { it.isEmpty() }
+ }
+ directInheritors = inheritors.first().sortedBy { it.classNodeNameWithOuterClass() }
+ indirectInheritors = inheritors.drop(1).flatten().toList().sortedBy { it.classNodeNameWithOuterClass() }
+ }
+
+ val isCompanion = node.details(NodeKind.Modifier).any { it.name == "companion" }
+ val hasMeaningfulCompanion = !isCompanion && node.companion != null
+
+ private fun DocumentationNode.thisTypeExtension() =
+ detail(NodeKind.Receiver).detail(NodeKind.Type).links.any { it == node }
+
+ val functionKind = if (!isCompanion) NodeKind.Function else NodeKind.CompanionObjectFunction
+ val propertyKind = if (!isCompanion) NodeKind.Property else NodeKind.CompanionObjectProperty
+
+ private fun DocumentationNode.isFunction() = kind == functionKind
+ private fun DocumentationNode.isProperty() = kind == propertyKind
+
+
+ val nestedClasses = node.members.filter { it.kind in NodeKind.classLike } - enumValues
+
+ val attributes = node.attributes
+
+ val inheritedAttributes =
+ node.superclassTypeSequence
+ .toList()
+ .sortedBy { it.name }
+ .flatMap { it.typeDeclarationClass?.attributes.orEmpty() }
+ .distinctBy { it.attributeRef!!.name }
+ .groupBy { it.owner!! }
+
+ val allInheritedMembers = node.allInheritedMembers
+ val constants = node.members.filter { it.constantValue() != null }
+ val inheritedConstants = allInheritedMembers.filter { it.constantValue() != null }.groupBy { it.owner!! }
+
+
+ fun compareVisibilities(a: String, b: String): Int {
+ return visibilityNames.indexOf(a) - visibilityNames.indexOf(b)
+ }
+
+ fun Collection<DocumentationNode>.groupByVisibility() =
+ groupBy { it.visibility() }.toSortedMap(Comparator { a, b -> compareVisibilities(a, b) })
+
+
+ val constructors = node.members(NodeKind.Constructor).groupByVisibility()
+ val functions = node.members(functionKind).groupByVisibility()
+ val fields = (node.members(NodeKind.Field) - constants).groupByVisibility()
+ val properties = node.members(propertyKind) - constants
+ val inheritedFunctionsByReceiver = allInheritedMembers.filter { it.kind == functionKind }.groupBy { it.owner!! }
+ val inheritedPropertiesByReceiver =
+ allInheritedMembers.filter {
+ it.kind == propertyKind && it.constantValue() == null
+ }.groupBy { it.owner!! }
+
+ val inheritedFieldsByReceiver =
+ allInheritedMembers.filter {
+ it == NodeKind.Field && it.constantValue() != null
+ }.groupBy { it.owner!! }
+
+ val originalExtensions = if (!isCompanion) node.extensions else node.owner!!.extensions
+
+ val extensionFunctions: Map<DocumentationNode, List<DocumentationNode>>
+ val extensionProperties: Map<DocumentationNode, List<DocumentationNode>>
+ val inheritedExtensionFunctions: Map<DocumentationNode, List<DocumentationNode>>
+ val inheritedExtensionProperties: Map<DocumentationNode, List<DocumentationNode>>
+
+ init {
+ val (extensions, inheritedExtensions) = originalExtensions.partition { it.thisTypeExtension() }
+ extensionFunctions = extensions.filter { it.isFunction() }.sortedBy { it.name }.groupBy { it.owner!! }
+ extensionProperties = extensions.filter { it.isProperty() }.sortedBy { it.name }.groupBy { it.owner!! }
+ inheritedExtensionFunctions =
+ inheritedExtensions.filter { it.isFunction() }.sortedBy { it.name }.groupBy { it.owner!! }
+ inheritedExtensionProperties =
+ inheritedExtensions.filter { it.isProperty() }.sortedBy { it.name }.groupBy { it.owner!! }
+ }
+
+ val companionFunctions = node.members(NodeKind.CompanionObjectFunction).takeUnless { isCompanion }.orEmpty()
+ val companionProperties =
+ node.members(NodeKind.CompanionObjectProperty).takeUnless { isCompanion }.orEmpty() - constants
+
+
+ }
+
+ class PackagePage(val node: DocumentationNode) : Page() {
+
+ init {
+ assert(node.kind == NodeKind.Package)
+ }
+
+ val classes = node.members(NodeKind.Class)
+ val exceptions = node.members(NodeKind.Exception)
+ val typeAliases = node.members(NodeKind.TypeAlias)
+ val annotations = node.members(NodeKind.AnnotationClass)
+ val enums = node.members(NodeKind.Enum)
+
+ val constants = node.members(NodeKind.Property).filter { it.constantValue() != null }
+
+
+ private fun DocumentationNode.getClassExtensionReceiver() =
+ detailOrNull(NodeKind.Receiver)?.detailOrNull(NodeKind.Type)?.takeIf {
+ it.links.any { it.kind == NodeKind.ExternalLink || it.kind in NodeKind.classLike }
+ }
+
+ private fun List<DocumentationNode>.groupedExtensions() =
+ filter { it.getClassExtensionReceiver() != null }
+ .groupBy {
+ val receiverType = it.getClassExtensionReceiver()!!
+ receiverType.links(NodeKind.ExternalLink).firstOrNull()
+ ?: receiverType.links.first { it.kind in NodeKind.classLike}
+ }
+
+ private fun List<DocumentationNode>.externalExtensions(kind: NodeKind) =
+ associateBy({ it }, { it.members(kind) })
+ .filterNot { (_, values) -> values.isEmpty() }
+
+ val extensionFunctions =
+ node.members(NodeKind.ExternalClass).externalExtensions(NodeKind.Function) +
+ node.members(NodeKind.Function).groupedExtensions()
+
+ val extensionProperties =
+ node.members(NodeKind.ExternalClass).externalExtensions(NodeKind.Property) +
+ node.members(NodeKind.Property).groupedExtensions()
+
+ val functions = node.members(NodeKind.Function) - extensionFunctions.values.flatten()
+ val properties = node.members(NodeKind.Property) - constants - extensionProperties.values.flatten()
+
+ }
+ }
+}
+
+class JavaLayoutHtmlFormatOutputBuilderFactoryImpl @Inject constructor(
+ val uriProvider: JavaLayoutHtmlUriProvider,
+ val languageService: LanguageService,
+ val templateService: JavaLayoutHtmlTemplateService,
+ val logger: DokkaLogger
+) : JavaLayoutHtmlFormatOutputBuilderFactory {
+ override fun createOutputBuilder(output: Appendable, node: DocumentationNode): JavaLayoutHtmlFormatOutputBuilder {
+ return createOutputBuilder(output, uriProvider.mainUri(node))
+ }
+
+ override fun createOutputBuilder(output: Appendable, uri: URI): JavaLayoutHtmlFormatOutputBuilder {
+ return JavaLayoutHtmlFormatOutputBuilder(output, languageService, uriProvider, templateService, logger, uri)
+ }
+}
+
+fun DocumentationNode.constantValue(): String? =
+ detailOrNull(NodeKind.Value)?.name.takeIf {
+ kind == NodeKind.Field || kind == NodeKind.Property || kind == NodeKind.CompanionObjectProperty
+ }
+
+
+private val visibilityNames = setOf("public", "protected", "internal", "package-local", "private")
+
+fun DocumentationNode.visibility(): String =
+ details(NodeKind.Modifier).firstOrNull { it.name in visibilityNames }?.name ?: "" \ No newline at end of file