<lambda>null1 package org.jetbrains.dokka.Formats
2 
3 import com.google.common.base.Throwables
4 import kotlinx.html.*
5 import kotlinx.html.Entities.nbsp
6 import kotlinx.html.stream.appendHTML
7 import org.jetbrains.dokka.*
8 import org.jetbrains.dokka.LanguageService.RenderMode.FULL
9 import org.jetbrains.dokka.LanguageService.RenderMode.SUMMARY
10 import org.jetbrains.dokka.NodeKind.Companion.classLike
11 import java.net.URI
12 import javax.inject.Inject
13 
14 
15 open class JavaLayoutHtmlFormatOutputBuilder(
16     val output: Appendable,
17     val languageService: LanguageService,
18     val uriProvider: JavaLayoutHtmlUriProvider,
19     val templateService: JavaLayoutHtmlTemplateService,
20     val logger: DokkaLogger,
21     val uri: URI
22 ) {
23 
24     val htmlConsumer = output.appendHTML()
25 
26 
27     private fun FlowContent.hN(
28         level: Int,
29         classes: String? = null,
30         block: CommonAttributeGroupFacadeFlowHeadingPhrasingContent.() -> Unit
31     ) {
32         when (level) {
33             1 -> h1(classes, block)
34             2 -> h2(classes, block)
35             3 -> h3(classes, block)
36             4 -> h4(classes, block)
37             5 -> h5(classes, block)
38             6 -> h6(classes, block)
39         }
40     }
41 
42     protected open fun FlowContent.metaMarkup(content: List<ContentNode>, contextUri: URI = uri) =
43             contentNodesToMarkup(content, contextUri)
44 
45     protected fun FlowContent.nodeContent(node: DocumentationNode, uriNode: DocumentationNode) =
46         contentNodeToMarkup(node.content, uriProvider.mainUriOrWarn(uriNode) ?: uri)
47 
48     protected fun FlowContent.nodeContent(node: DocumentationNode) =
49         nodeContent(node, node)
50 
51     protected fun FlowContent.contentNodesToMarkup(content: List<ContentNode>, contextUri: URI = uri): Unit =
52         content.forEach { contentNodeToMarkup(it, contextUri) }
53 
54     protected fun FlowContent.contentNodeToMarkup(content: ContentNode, contextUri: URI = uri) {
55         when (content) {
56             is ContentText -> +content.text
57             is ContentSymbol -> span("symbol") { +content.text }
58             is ContentKeyword -> span("keyword") { +content.text }
59             is ContentIdentifier -> span("identifier") {
60                 content.signature?.let { id = it }
61                 +content.text
62             }
63 
64             is ContentHeading -> hN(level = content.level) { contentNodesToMarkup(content.children, contextUri) }
65 
66             is ContentEntity -> +content.text
67 
68             is ContentStrong -> strong { contentNodesToMarkup(content.children, contextUri) }
69             is ContentStrikethrough -> del { contentNodesToMarkup(content.children, contextUri) }
70             is ContentEmphasis -> em { contentNodesToMarkup(content.children, contextUri) }
71 
72             is ContentOrderedList -> ol { contentNodesToMarkup(content.children, contextUri) }
73             is ContentUnorderedList -> ul { contentNodesToMarkup(content.children, contextUri) }
74             is ContentListItem -> consumer.li {
75                 (content.children.singleOrNull() as? ContentParagraph)
76                         ?.let { paragraph -> contentNodesToMarkup(paragraph.children, contextUri) }
77                         ?: contentNodesToMarkup(content.children, contextUri)
78             }
79 
80             is ContentDescriptionList -> dl { contentNodesToMarkup(content.children, contextUri) }
81             is ContentDescriptionTerm -> consumer.dt {
82                 (content.children.singleOrNull() as? ContentParagraph)
83                     ?.let { paragraph -> this@contentNodeToMarkup.contentNodesToMarkup(paragraph.children, contextUri) }
84                         ?: this@contentNodeToMarkup.contentNodesToMarkup(content.children, contextUri)
85             }
86             is ContentDescriptionDefinition -> consumer.dd {
87                 (content.children.singleOrNull() as? ContentParagraph)
88                     ?.let { paragraph -> contentNodesToMarkup(paragraph.children, contextUri) }
89                         ?: contentNodesToMarkup(content.children, contextUri)
90             }
91 
92             is ContentTable -> table { contentNodesToMarkup(content.children, contextUri) }
93             is ContentTableBody -> consumer.tbody { this@contentNodeToMarkup.contentNodesToMarkup(content.children, contextUri) }
94             is ContentTableRow -> consumer.tr { this@contentNodeToMarkup.contentNodesToMarkup(content.children, contextUri) }
95             is ContentTableHeader -> consumer.th {
96                 content.colspan?.let {
97                     if (it.isNotBlank()) {
98                         attributes["colspan"] = content.colspan
99                     }
100                 }
101                 content.rowspan?.let {
102                     if (it.isNotBlank()) {
103                         attributes["rowspan"] = content.rowspan
104                     }
105                 }
106                 (content.children.singleOrNull() as? ContentParagraph)
107                     ?.let { paragraph -> this@contentNodeToMarkup.contentNodesToMarkup(paragraph.children, contextUri) }
108                         ?: this@contentNodeToMarkup.contentNodesToMarkup(content.children, contextUri)
109             }
110             is ContentTableCell -> consumer.td {
111                 content.colspan?.let {
112                     if (it.isNotBlank()) {
113                         attributes["colspan"] = content.colspan
114                     }
115                 }
116                 content.rowspan?.let {
117                     if (it.isNotBlank()) {
118                         attributes["rowspan"] = content.rowspan
119                     }
120                 }
121                 (content.children.singleOrNull() as? ContentParagraph)
122                     ?.let { paragraph -> contentNodesToMarkup(paragraph.children, contextUri) }
123                         ?: contentNodesToMarkup(content.children, contextUri)
124             }
125 
126             is ContentSpecialReference -> aside(classes = "note") {
127                 contentNodesToMarkup(content.children, contextUri)
128             }
129 
130             is ContentCode -> contentInlineCode(content)
131             is ContentBlockSampleCode -> contentBlockSampleCode(content)
132             is ContentBlockCode -> contentBlockCode(content)
133 
134             ContentNonBreakingSpace -> +nbsp
135             ContentSoftLineBreak, ContentIndentedSoftLineBreak -> {
136             }
137             ContentHardLineBreak -> br
138 
139             is ContentParagraph -> p(classes = content.label) { contentNodesToMarkup(content.children, contextUri) }
140 
141             is NodeRenderContent -> renderedSignature(content.node, mode = content.mode)
142             is ContentNodeLink -> {
143                 fun FlowContent.body() = contentNodesToMarkup(content.children, contextUri)
144 
145                 when (content.node?.kind) {
146                     NodeKind.TypeParameter -> body()
147                     else -> a(href = content.node, block = FlowContent::body)
148                 }
149             }
150             is ContentBookmark -> a {
151                 id = content.name
152                 contentNodesToMarkup(content.children, contextUri)
153             }
154             is ContentExternalLink -> contentExternalLink(content)
155             is ContentLocalLink -> a(href = contextUri.resolve(content.href).relativeTo(uri).toString()) {
156                 contentNodesToMarkup(content.children, contextUri)
157             }
158             is ContentSection -> {
159             }
160             is ScriptBlock -> script(content.type, content.src) {}
161             is ContentBlock -> contentNodesToMarkup(content.children, contextUri)
162         }
163     }
164 
165     protected open fun FlowContent.contentInlineCode(content: ContentCode) {
166         code { contentNodesToMarkup(content.children) }
167     }
168 
169     protected open fun FlowContent.contentBlockSampleCode(content: ContentBlockSampleCode) {
170         pre {
171             code {
172                 attributes["data-language"] = content.language
173                 contentNodesToMarkup(content.importsBlock.children)
174                 +"\n\n"
175                 contentNodesToMarkup(content.children)
176             }
177         }
178     }
179 
180     protected open fun FlowContent.contentBlockCode(content: ContentBlockCode) {
181         pre {
182             code {
183                 attributes["data-language"] = content.language
184                 contentNodesToMarkup(content.children)
185             }
186         }
187     }
188 
189     protected open fun FlowContent.contentExternalLink(content: ContentExternalLink) {
190         a(href = content.href) { contentNodesToMarkup(content.children) }
191     }
192 
193     protected open fun <T> FlowContent.summaryNodeGroup(
194         nodes: Iterable<T>,
195         header: String,
196         headerAsRow: Boolean = true,
197         row: TBODY.(T) -> Unit
198     ) {
199         if (nodes.none()) return
200         if (!headerAsRow) {
201             h2 { +header }
202         }
203         table {
204             tbody {
205                 if (headerAsRow) {
206                     developerHeading(header)
207                 }
208                 nodes.forEach { node ->
209                     row(node)
210                 }
211             }
212         }
213     }
214 
215 
216     protected open fun summary(node: DocumentationNode) = node.summary
217 
218     protected open fun TBODY.classLikeRow(node: DocumentationNode) = tr {
219         td { a(href = uriProvider.linkTo(node, uri)) { +node.simpleName() } }
220         td { nodeSummary(node) }
221     }
222 
223     protected fun FlowContent.modifiers(node: DocumentationNode) {
224         for (modifier in node.details(NodeKind.Modifier)) {
225             renderedSignature(modifier, SUMMARY)
226         }
227     }
228 
229     protected fun FlowContent.shortFunctionParametersList(func: DocumentationNode) {
230         val params = func.details(NodeKind.Parameter)
231             .map { languageService.render(it, FULL) }
232             .run {
233                 drop(1).fold(listOfNotNull(firstOrNull())) { acc, node ->
234                     acc + ContentText(", ") + node
235                 }
236             }
237         metaMarkup(listOf(ContentText("(")) + params + listOf(ContentText(")")))
238     }
239 
240 
241     protected open fun TBODY.functionLikeSummaryRow(node: DocumentationNode) = tr {
242         if (node.kind != NodeKind.Constructor) {
243             td {
244                 modifiers(node)
245                 renderedSignature(node.detail(NodeKind.Type), SUMMARY)
246             }
247         }
248         td {
249             div {
250                 code {
251                     val receiver = node.detailOrNull(NodeKind.Receiver)
252                     if (receiver != null) {
253                         renderedSignature(receiver.detail(NodeKind.Type), SUMMARY)
254                         +"."
255                     }
256                     a(href = node) { +node.prettyName }
257                     shortFunctionParametersList(node)
258                 }
259             }
260 
261             nodeSummary(node)
262         }
263     }
264 
265     protected open fun TBODY.propertyLikeSummaryRow(node: DocumentationNode, showSignature: Boolean = true) = tr {
266         if (showSignature) {
267             td {
268                 modifiers(node)
269                 renderedSignature(node.detail(NodeKind.Type), SUMMARY)
270             }
271         }
272         td {
273             div {
274                 code {
275                     a(href = node) { +node.name }
276                 }
277             }
278 
279             nodeSummary(node)
280         }
281     }
282 
283     protected open fun TBODY.nestedClassSummaryRow(node: DocumentationNode) = tr {
284         td {
285             modifiers(node)
286         }
287         td {
288             div {
289                 code {
290                     a(href = node) { +node.name }
291                 }
292             }
293 
294             nodeSummary(node)
295         }
296     }
297 
298     protected fun HtmlBlockTag.nodeSummary(node: DocumentationNode, uriNode: DocumentationNode) {
299         contentNodeToMarkup(summary(node), uriProvider.mainUriOrWarn(uriNode) ?: uri)
300     }
301 
302     protected fun HtmlBlockTag.nodeSummary(node: DocumentationNode) {
303         nodeSummary(node, node)
304     }
305 
306     protected open fun TBODY.inheritRow(
307         entry: Map.Entry<DocumentationNode, List<DocumentationNode>>,
308         summaryRow: TBODY.(DocumentationNode) -> Unit
309     ) = tr {
310         td {
311             val (from, nodes) = entry
312             +"From class "
313             a(href = from.owner!!) { +from.qualifiedName() }
314             table {
315                 tbody {
316                     for (node in nodes) {
317                         summaryRow(node)
318                     }
319                 }
320             }
321         }
322     }
323 
324     protected open fun TBODY.groupedRow(
325         entry: Map.Entry<DocumentationNode, List<DocumentationNode>>,
326         groupHeader: HtmlBlockTag.(DocumentationNode) -> Unit,
327         summaryRow: TBODY.(DocumentationNode) -> Unit
328     ) = tr {
329         td {
330             val (from, nodes) = entry
331             groupHeader(from)
332             table {
333                 tbody {
334                     for (node in nodes) {
335                         summaryRow(node)
336                     }
337                 }
338             }
339         }
340     }
341 
342     protected open fun TBODY.extensionRow(
343         entry: Map.Entry<DocumentationNode, List<DocumentationNode>>,
344         summaryRow: TBODY.(DocumentationNode) -> Unit
345     ) = groupedRow(entry, { from ->
346         +"From "
347         a(href = from) { +from.qualifiedName() }
348     }, summaryRow)
349 
350 
351     protected open fun TBODY.extensionByReceiverRow(
352         entry: Map.Entry<DocumentationNode, List<DocumentationNode>>,
353         summaryRow: TBODY.(DocumentationNode) -> Unit
354     ) = groupedRow(entry, { from ->
355         +"For "
356         a(href = from) { +from.name }
357     }, summaryRow)
358 
359     protected open fun FlowOrInteractiveOrPhrasingContent.a(href: DocumentationNode?, classes: String? = null, block: HtmlBlockInlineTag.() -> Unit) {
360         if (href == null) {
361             return a(href = "#", classes = classes, block = block)
362         }
363 
364         val hrefText = try {
365             href.name.takeIf { href.kind == NodeKind.ExternalLink }
366                     ?: href.links.firstOrNull { it.kind == NodeKind.ExternalLink }?.name
367                     ?: "#".takeIf { href.kind == NodeKind.ExternalClass } // When external class unresolved
368                     ?: uriProvider.linkTo(href, uri)
369         } catch (e: Exception) {
370             val owners = generateSequence(href) { it.owner }.toList().reversed()
371             logger.warn("Exception while resolving link to ${owners.joinToString(separator = " ")}\n"
372                     + Throwables.getStackTraceAsString(e))
373             "#"
374         }
375 
376         a(href = hrefText, classes = classes, block = block)
377     }
378 
379     protected open fun FlowContent.renderedSignature(
380         node: DocumentationNode,
381         mode: LanguageService.RenderMode = SUMMARY
382     ) {
383         contentNodeToMarkup(languageService.render(node, mode), uri)
384     }
385 
386     protected open fun generatePackage(page: Page.PackagePage) = templateService.composePage(
387         page,
388         htmlConsumer,
389         headContent = {
390 
391         },
392         bodyContent = {
393             h1 { +page.node.name }
394             nodeContent(page.node)
395             this@composePage.summaryNodeGroup(page.interfaces, "Interfaces", headerAsRow = false) { classLikeRow(it) }
396             this@composePage.summaryNodeGroup(page.classes, "Classes", headerAsRow = false) { classLikeRow(it) }
397             this@composePage.summaryNodeGroup(page.exceptions, "Exceptions", headerAsRow = false) { classLikeRow(it) }
398             this@composePage.summaryNodeGroup(page.typeAliases, "Type-aliases", headerAsRow = false) { classLikeRow(it) }
399             this@composePage.summaryNodeGroup(page.annotations, "Annotations", headerAsRow = false) { classLikeRow(it) }
400             this@composePage.summaryNodeGroup(page.enums, "Enums", headerAsRow = false) { classLikeRow(it) }
401 
402             this@composePage.summaryNodeGroup(
403                 page.constants,
404                 "Top-level constants summary",
405                 headerAsRow = false
406             ) {
407                 propertyLikeSummaryRow(it)
408             }
409 
410             this@composePage.summaryNodeGroup(
411                 page.functions,
412                 "Top-level functions summary",
413                 headerAsRow = false
414             ) {
415                 functionLikeSummaryRow(it)
416             }
417 
418             this@composePage.summaryNodeGroup(
419                 page.properties,
420                 "Top-level properties summary",
421                 headerAsRow = false
422             ) {
423                 propertyLikeSummaryRow(it)
424             }
425 
426             this@composePage.summaryNodeGroup(
427                 page.extensionFunctions.entries,
428                 "Extension functions summary",
429                 headerAsRow = false
430             ) {
431                 extensionByReceiverRow(it) {
432                     functionLikeSummaryRow(it)
433                 }
434             }
435 
436             this@composePage.summaryNodeGroup(
437                 page.extensionProperties.entries,
438                 "Extension properties summary",
439                 headerAsRow = false
440             ) {
441                 extensionByReceiverRow(it) {
442                     functionLikeSummaryRow(it)
443                 }
444             }
445 
446             fullMemberDocs(page.constants, "Top-level constants")
447             fullMemberDocs(page.functions, "Top-level functions")
448             fullMemberDocs(page.properties, "Top-level properties")
449             fullMemberDocs(page.extensionFunctions.values.flatten(), "Extension functions")
450             fullMemberDocs(page.extensionProperties.values.flatten(), "Extension properties")
451         }
452     )
453 
454     protected fun FlowContent.qualifiedTypeReference(node: DocumentationNode) {
455         if (node.kind in classLike) {
456             a(href = node) { +node.qualifiedName() }
457             return
458         }
459 
460         val targetLink = node.links.firstOrNull()
461 
462         if (targetLink?.kind == NodeKind.TypeParameter) {
463             +node.name
464             return
465         }
466 
467         a(href = targetLink) {
468             +node.qualifiedNameFromType()
469         }
470         val typeParameters = node.details(NodeKind.Type)
471         if (typeParameters.isNotEmpty()) {
472             +"<"
473             typeParameters.forEach {
474                 if (it != typeParameters.first()) {
475                     +", "
476                 }
477                 qualifiedTypeReference(it)
478             }
479             +">"
480         }
481     }
482 
483     protected open fun FlowContent.classHierarchy(superclasses: List<DocumentationNode>) {
484         table {
485             superclasses.forEach {
486                 tr {
487                     if (it != superclasses.first()) {
488                         td {
489                             +"   ↳"
490                         }
491                     }
492                     td {
493                         qualifiedTypeReference(it)
494                     }
495                 }
496             }
497         }
498     }
499 
500     protected open fun FlowContent.subclasses(inheritors: List<DocumentationNode>, direct: Boolean) {
501         if (inheritors.isEmpty()) return
502         div {
503             table {
504                 thead {
505                     tr {
506                         td {
507                             if (direct)
508                                 +"Known Direct Subclasses"
509                             else
510                                 +"Known Indirect Subclasses"
511                         }
512                     }
513                 }
514                 tbody {
515                     inheritors.forEach { inheritor ->
516                         tr {
517                             td {
518                                 a(href = inheritor) { +inheritor.classNodeNameWithOuterClass() }
519                             }
520                             td {
521                                 nodeSummary(inheritor)
522                             }
523                         }
524                     }
525                 }
526             }
527         }
528     }
529 
530     protected open fun FlowContent.classLikeSummaries(page: Page.ClassPage) = with(page) {
531         this@classLikeSummaries.summaryNodeGroup(
532             nestedClasses,
533             "Nested classes",
534             headerAsRow = true
535         ) {
536             nestedClassSummaryRow(it)
537         }
538 
539         this@classLikeSummaries.summaryNodeGroup(enumValues, "Enum values") {
540             propertyLikeSummaryRow(it)
541         }
542 
543         this@classLikeSummaries.summaryNodeGroup(constants, "Constants") { propertyLikeSummaryRow(it) }
544 
545         constructors.forEach { (visibility, group) ->
546             this@classLikeSummaries.summaryNodeGroup(
547                     group,
548                     "${visibility.capitalize()} constructors",
549                     headerAsRow = true
550             ) {
551                 functionLikeSummaryRow(it)
552             }
553         }
554 
555         functions.forEach { (visibility, group) ->
556             this@classLikeSummaries.summaryNodeGroup(
557                     group,
558                     "${visibility.capitalize()} functions",
559                     headerAsRow = true
560             ) {
561                 functionLikeSummaryRow(it)
562             }
563         }
564 
565         this@classLikeSummaries.summaryNodeGroup(
566             companionFunctions,
567             "Companion functions",
568             headerAsRow = true
569         ) {
570             functionLikeSummaryRow(it)
571         }
572         this@classLikeSummaries.summaryNodeGroup(
573             inheritedFunctionsByReceiver.entries,
574             "Inherited functions",
575             headerAsRow = true
576         ) {
577             inheritRow(it) {
578                 functionLikeSummaryRow(it)
579             }
580         }
581         this@classLikeSummaries.summaryNodeGroup(
582             extensionFunctions.entries,
583             "Extension functions",
584             headerAsRow = true
585         ) {
586             extensionRow(it) {
587                 functionLikeSummaryRow(it)
588             }
589         }
590         this@classLikeSummaries.summaryNodeGroup(
591             inheritedExtensionFunctions.entries,
592             "Inherited extension functions",
593             headerAsRow = true
594         ) {
595             extensionRow(it) {
596                 functionLikeSummaryRow(it)
597             }
598         }
599 
600 
601         this@classLikeSummaries.summaryNodeGroup(properties, "Properties", headerAsRow = true) { propertyLikeSummaryRow(it) }
602         this@classLikeSummaries.summaryNodeGroup(
603             companionProperties,
604             "Companion properties",
605             headerAsRow = true
606         ) {
607             propertyLikeSummaryRow(it)
608         }
609 
610         this@classLikeSummaries.summaryNodeGroup(
611             inheritedPropertiesByReceiver.entries,
612             "Inherited properties",
613             headerAsRow = true
614         ) {
615             inheritRow(it) {
616                 propertyLikeSummaryRow(it)
617             }
618         }
619         this@classLikeSummaries.summaryNodeGroup(
620             extensionProperties.entries,
621             "Extension properties",
622             headerAsRow = true
623         ) {
624             extensionRow(it) {
625                 propertyLikeSummaryRow(it)
626             }
627         }
628         this@classLikeSummaries.summaryNodeGroup(
629             inheritedExtensionProperties.entries,
630             "Inherited extension properties",
631             headerAsRow = true
632         ) {
633             extensionRow(it) {
634                 propertyLikeSummaryRow(it)
635             }
636         }
637     }
638 
639     protected open fun FlowContent.classLikeFullMemberDocs(page: Page.ClassPage) = with(page) {
640         fullMemberDocs(enumValues, "Enum values")
641         fullMemberDocs(constants, "Constants")
642 
643         constructors.forEach { (visibility, group) ->
644             fullMemberDocs(group, "${visibility.capitalize()} constructors")
645         }
646 
647         functions.forEach { (visibility, group) ->
648             fullMemberDocs(group, "${visibility.capitalize()} methods")
649         }
650 
651         fullMemberDocs(properties, "Properties")
652         if (!hasMeaningfulCompanion) {
653             fullMemberDocs(companionFunctions, "Companion functions")
654             fullMemberDocs(companionProperties, "Companion properties")
655         }
656     }
657 
658     protected open fun generateClassLike(page: Page.ClassPage) = templateService.composePage(
659         page,
660         htmlConsumer,
661         headContent = {
662 
663         },
664         bodyContent = {
665             val node = page.node
666             with(page) {
667 
668                 div {
669                     id = "api-info-block"
670                     apiAndDeprecatedVersions(node)
671                 }
672 
673                 if (node.artifactId.name.isNotEmpty()) {
674                     div(classes = "api-level") { br { +"belongs to Maven artifact ${node.artifactId}" } }
675                 }
676                 h1 { +node.name }
677                 pre { renderedSignature(node, FULL) }
678                 classHierarchy(page.superclasses)
679 
680                 subclasses(page.directInheritors, true)
681                 subclasses(page.indirectInheritors, false)
682 
683                 deprecatedClassCallOut(node)
684                 nodeContent(node)
685 
686                 h2 { +"Summary" }
687                 classLikeSummaries(page)
688                 classLikeFullMemberDocs(page)
689             }
690         }
691     )
692 
693     protected open fun FlowContent.classIndexSummary(node: DocumentationNode) {
694         nodeContent(node)
695     }
696 
697     protected open fun generateClassIndex(page: Page.ClassIndex) = templateService.composePage(
698         page,
699         htmlConsumer,
700         headContent = {
701 
702         },
703         bodyContent = {
704             h1 { +"Class Index" }
705 
706 
707             ul {
708                 page.classesByFirstLetter.forEach { (letter) ->
709                     li { a(href = "#letter_$letter") { +letter } }
710                 }
711             }
712 
713             page.classesByFirstLetter.forEach { (letter, classes) ->
714                 h2 {
715                     id = "letter_$letter"
716                     +letter
717                 }
718                 table {
719                     tbody {
720                         for (node in classes) {
721                             tr {
722                                 td {
723                                     a(href = uriProvider.linkTo(node, uri)) { +node.classNodeNameWithOuterClass() }
724                                 }
725                                 td {
726                                     if (!deprecatedIndexSummary(node)) {
727                                         classIndexSummary(node)
728                                     }
729                                 }
730                             }
731                         }
732                     }
733                 }
734             }
735         }
736     )
737 
738     protected open fun generatePackageIndex(page: Page.PackageIndex) = templateService.composePage(
739         page,
740         htmlConsumer,
741         headContent = {
742 
743         },
744         bodyContent = {
745             h1 { +"Package Index" }
746             table {
747                 tbody {
748                     for (node in page.packages) {
749                         tr {
750                             td {
751                                 a(href = uriProvider.linkTo(node, uri)) { +node.name }
752                             }
753                             td {
754                                 nodeContent(node)
755                             }
756                         }
757                     }
758                 }
759             }
760         }
761     )
762 
763     fun generatePage(page: Page) {
764         when (page) {
765             is Page.PackageIndex -> generatePackageIndex(page)
766             is Page.ClassIndex -> generateClassIndex(page)
767             is Page.ClassPage -> generateClassLike(page)
768             is Page.PackagePage -> generatePackage(page)
769         }
770     }
771 
772     protected fun FlowContent.fullMemberDocs(
773         nodes: List<DocumentationNode>,
774         header: String
775     ) {
776         if (nodes.none()) return
777         h2 {
778             +header
779         }
780         for (node in nodes) {
781             fullMemberDocs(node)
782         }
783     }
784 
785     protected open fun FlowContent.seeAlsoSection(links: List<List<ContentNode>>) {
786         p { b { +"See Also" } }
787         ul {
788             links.forEach { linkParts ->
789                 li { code { metaMarkup(linkParts) } }
790             }
791         }
792     }
793 
794     protected open fun FlowContent.regularSection(name: String, entries: List<ContentSection>) {
795         table {
796             thead {
797                 tr {
798                     th {
799                         colSpan = "2"
800                         +name
801                     }
802                 }
803             }
804             tbody {
805                 entries.forEach {
806                     tr {
807                         if (it.subjectName != null) {
808                             td { +it.subjectName }
809                         }
810                         td {
811                             metaMarkup(it.children)
812                         }
813                     }
814                 }
815             }
816         }
817     }
818 
819     protected open fun FlowContent.deprecationWarningToMarkup(
820         node: DocumentationNode,
821         prefix: Boolean = false,
822         emphasis: Boolean = true
823     ): Boolean {
824         val deprecated = formatDeprecationOrNull(node, prefix, emphasis)
825         deprecated?.let {
826             contentNodeToMarkup(deprecated, uriProvider.mainUri(node))
827             return true
828         }
829         return false
830     }
831 
832     protected open fun FlowContent.deprecatedClassCallOut(node: DocumentationNode) {
833         val deprecatedLevelExists = node.deprecatedLevel.name.isNotEmpty()
834         if (deprecatedLevelExists) {
835             hr { }
836             aside(classes = "caution") {
837                 strong { +node.deprecatedLevelMessage() }
838                 deprecationWarningToMarkup(node, emphasis = false)
839             }
840         }
841     }
842 
843     protected open fun FlowContent.deprecatedIndexSummary(node: DocumentationNode): Boolean {
844         val deprecatedLevelExists = node.deprecatedLevel.name.isNotEmpty()
845         if (deprecatedLevelExists) {
846             val em = ContentEmphasis()
847             em.append(ContentText(node.deprecatedLevelMessage()))
848             em.append(ContentText(" "))
849             for (child in node.deprecation?.content?.children ?: emptyList<ContentNode>()) {
850                 em.append(child)
851             }
852             contentNodeToMarkup(em, uriProvider.mainUri(node))
853             return true
854         }
855         return false
856     }
857 
858     protected open fun FlowContent.apiAndDeprecatedVersions(node: DocumentationNode) {
859         val apiLevelExists = node.apiLevel.name.isNotEmpty()
860         val sdkExtSinceExists = node.sdkExtSince.name.isNotEmpty()
861         val deprecatedLevelExists = node.deprecatedLevel.name.isNotEmpty()
862         if (apiLevelExists || sdkExtSinceExists || deprecatedLevelExists) {
863             div(classes = "api-level") {
864                 if (apiLevelExists) {
865                     +"Added in "
866                     a(href = "https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels") {
867                         +"API level ${node.apiLevel.name}"
868                     }
869                 }
870                 if (sdkExtSinceExists) {
871                     if (apiLevelExists) {
872                         br
873                         +"Also in "
874                     } else {
875                         +"Added in "
876                     }
877                     a(href = "https://developer.android.com/sdkExtensions") {
878                         +"${node.sdkExtSince.name}"
879                     }
880                 }
881                 if (deprecatedLevelExists) {
882                     if (apiLevelExists || sdkExtSinceExists) {
883                         br
884                     }
885                     +"Deprecated in "
886                     a(href = "https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels") {
887                         +"API level ${node.deprecatedLevel.name}"
888                     }
889                 }
890             }
891         }
892     }
893 
894     protected open fun formatDeprecationOrNull(
895         node: DocumentationNode,
896         prefix: Boolean = false,
897         emphasis: Boolean = true): ContentNode? {
898         val deprecated = node.deprecation
899         deprecated?.let {
900             return ContentParagraph("caution").apply {
901                 if (prefix) {
902                     append(ContentStrong().apply { text(
903                         if (deprecated.content.children.size == 0) "Deprecated."
904                         else "Deprecated: "
905                     ) })
906                 }
907                 val em = if (emphasis) ContentEmphasis() else ContentBlock()
908                 for (child in deprecated.content.children) {
909                     em.append(child)
910                 }
911                 append(em)
912             }
913         }
914         return null
915     }
916 
917     protected open fun FlowContent.section(name: String, sectionParts: List<ContentSection>) {
918         when (name) {
919             ContentTags.SeeAlso -> seeAlsoSection(sectionParts.map { it.children.flatMap { (it as? ContentParagraph)?.children ?: listOf(it) } })
920             else -> regularSection(name, sectionParts)
921         }
922     }
923 
924     protected open fun FlowContent.sections(content: Content) {
925         val sectionsByTag = content.sections.groupByTo(mutableMapOf()) { it.tag }
926 
927         val seeAlso = sectionsByTag.remove(ContentTags.SeeAlso)
928 
929         for ((name, entries) in sectionsByTag) {
930             section(name, entries)
931         }
932 
933         seeAlso?.let { section(ContentTags.SeeAlso, it) }
934     }
935 
936     protected open fun FlowContent.fullMemberDocs(node: DocumentationNode, uriNode: DocumentationNode) {
937         div {
938             id = node.signatureForAnchor(logger)
939             h3 { +node.name }
940             pre { renderedSignature(node, FULL) }
941             deprecationWarningToMarkup(node, prefix = true)
942             nodeContent(node)
943             node.constantValue()?.let { value ->
944                 pre {
945                     +"Value: "
946                     code { +value }
947                 }
948             }
949 
950             sections(node.content)
951         }
952     }
953 
954     protected open fun FlowContent.fullMemberDocs(node: DocumentationNode) {
955         fullMemberDocs(node, node)
956     }
957 
958     sealed class Page {
959         class PackageIndex(packages: List<DocumentationNode>) : Page() {
960             init {
961                 assert(packages.all { it.kind == NodeKind.Package })
962             }
963 
964             val packages = packages.sortedBy { it.name }
965         }
966 
967         class ClassIndex(allTypesNode: DocumentationNode) : Page() {
968             init {
969                 assert(allTypesNode.kind == NodeKind.AllTypes)
970             }
971 
972             // Wide-collect all nested classes
973             val classes: List<DocumentationNode> =
974                 generateSequence(listOf(allTypesNode)) { nodes ->
975                     nodes
976                         .flatMap { it.members.filter { it.kind in NodeKind.classLike } }
977                         .takeUnless { it.isEmpty() }
978                 }.drop(1)
979                     .flatten()
980                     .sortedBy { it.classNodeNameWithOuterClass().toLowerCase() }
981                     .toList()
982 
983 
984             // Group all classes by it's first letter and sort
985             val classesByFirstLetter =
986                 classes
987                     .groupBy {
988                         it.classNodeNameWithOuterClass().first().toString()
989                     }
990                     .entries
991                     .sortedBy { (letter) ->
992                         val x = letter.toLowerCase()
993                         x
994                     }
995         }
996 
997         class ClassPage(val node: DocumentationNode) : Page() {
998 
999             init {
1000                 assert(node.kind in NodeKind.classLike)
1001             }
1002 
1003             val superclasses = (sequenceOf(node) + node.superclassTypeSequence).toList().asReversed()
1004 
1005             val enumValues = node.members(NodeKind.EnumItem).sortedBy { it.name }
1006 
1007             val directInheritors: List<DocumentationNode>
1008             val indirectInheritors: List<DocumentationNode>
1009 
1010             init {
1011                 // Wide-collect all inheritors
1012                 val inheritors = generateSequence(node.inheritors) { inheritors ->
1013                     inheritors
1014                         .flatMap { it.inheritors }
1015                         .takeUnless { it.isEmpty() }
1016                 }
1017                 directInheritors = inheritors.first().sortedBy { it.classNodeNameWithOuterClass() }
1018                 indirectInheritors = inheritors.drop(1).flatten().toList().sortedBy { it.classNodeNameWithOuterClass() }
1019             }
1020 
1021             val isCompanion = node.details(NodeKind.Modifier).any { it.name == "companion" }
1022             val hasMeaningfulCompanion = !isCompanion && node.companion != null
1023 
1024             private fun DocumentationNode.thisTypeExtension() =
1025                 detail(NodeKind.Receiver).detail(NodeKind.Type).links.any { it == node }
1026 
1027             val functionKind = if (!isCompanion) NodeKind.Function else NodeKind.CompanionObjectFunction
1028             val propertyKind = if (!isCompanion) NodeKind.Property else NodeKind.CompanionObjectProperty
1029 
1030             private fun DocumentationNode.isFunction() = kind == functionKind
1031             private fun DocumentationNode.isProperty() = kind == propertyKind
1032 
1033 
1034             val nestedClasses = node.members.filter { it.kind in NodeKind.classLike } - enumValues
1035 
1036             val attributes = node.attributes
1037 
1038             val inheritedAttributes =
1039                     node.superclassTypeSequence
1040                             .toList()
1041                             .sortedBy { it.name }
1042                             .flatMap { it.typeDeclarationClass?.attributes.orEmpty() }
1043                             .distinctBy { it.attributeRef!!.name }
1044                             .groupBy { it.owner!! }
1045 
1046             val allInheritedMembers = node.allInheritedMembers
1047             val constants = node.members.filter { it.constantValue() != null }
1048             val inheritedConstants = allInheritedMembers.filter { it.constantValue() != null }.groupBy { it.owner!! }
1049 
1050 
1051             fun compareVisibilities(a: String, b: String): Int {
1052                 return visibilityNames.indexOf(a) - visibilityNames.indexOf(b)
1053             }
1054 
1055             fun Collection<DocumentationNode>.groupByVisibility() =
1056                     groupBy { it.visibility() }.toSortedMap(Comparator { a, b -> compareVisibilities(a, b) })
1057 
1058 
1059             val constructors = node.members(NodeKind.Constructor).groupByVisibility()
1060             val functions = node.members(functionKind).groupByVisibility()
1061             val fields = (node.members(NodeKind.Field) - constants).groupByVisibility()
1062             val properties = node.members(propertyKind) - constants
1063             val inheritedFunctionsByReceiver = allInheritedMembers.filter { it.kind == functionKind }.groupBy { it.owner!! }
1064             val inheritedPropertiesByReceiver =
1065                 allInheritedMembers.filter {
1066                     it.kind == propertyKind && it.constantValue() == null
1067                 }.groupBy { it.owner!! }
1068 
1069             val inheritedFieldsByReceiver =
1070                 allInheritedMembers.filter {
1071                     it.kind == NodeKind.Field && it.constantValue() != null
1072                 }.groupBy { it.owner!! }
1073 
1074             val originalExtensions = if (!isCompanion) node.extensions else node.owner!!.extensions
1075 
1076             val extensionFunctions: Map<DocumentationNode, List<DocumentationNode>>
1077             val extensionProperties: Map<DocumentationNode, List<DocumentationNode>>
1078             val inheritedExtensionFunctions: Map<DocumentationNode, List<DocumentationNode>>
1079             val inheritedExtensionProperties: Map<DocumentationNode, List<DocumentationNode>>
1080 
1081             init {
1082                 val (extensions, inheritedExtensions) = originalExtensions.partition { it.thisTypeExtension() }
1083                 extensionFunctions = extensions.filter { it.isFunction() }.sortedBy { it.name }.groupBy { it.owner!! }
1084                 extensionProperties = extensions.filter { it.isProperty() }.sortedBy { it.name }.groupBy { it.owner!! }
1085                 inheritedExtensionFunctions =
1086                         inheritedExtensions.filter { it.isFunction() }.sortedBy { it.name }.groupBy { it.owner!! }
1087                 inheritedExtensionProperties =
1088                         inheritedExtensions.filter { it.isProperty() }.sortedBy { it.name }.groupBy { it.owner!! }
1089             }
1090 
1091             val companionFunctions = node.members(NodeKind.CompanionObjectFunction).takeUnless { isCompanion }.orEmpty()
1092             val companionProperties =
1093                 node.members(NodeKind.CompanionObjectProperty).takeUnless { isCompanion }.orEmpty() - constants
1094 
1095 
1096         }
1097 
1098         class PackagePage(val node: DocumentationNode) : Page() {
1099 
1100             init {
1101                 assert(node.kind == NodeKind.Package)
1102             }
1103 
1104             val interfaces = node.members(NodeKind.Interface) +
1105                     node.members(NodeKind.Class).flatMap { it.members(NodeKind.Interface) }
1106             val classes = node.members(NodeKind.Class)
1107             val exceptions = node.members(NodeKind.Exception)
1108             val typeAliases = node.members(NodeKind.TypeAlias)
1109             val annotations = node.members(NodeKind.AnnotationClass)
1110             val enums = node.members(NodeKind.Enum)
1111 
1112             val constants = node.members(NodeKind.Property).filter { it.constantValue() != null }
1113 
1114 
1115             private fun DocumentationNode.getClassExtensionReceiver() =
1116                 detailOrNull(NodeKind.Receiver)?.detailOrNull(NodeKind.Type)?.takeIf {
1117                     it.links.any { it.kind == NodeKind.ExternalLink || it.kind in NodeKind.classLike }
1118                 }
1119 
1120             private fun List<DocumentationNode>.groupedExtensions() =
1121                 filter { it.getClassExtensionReceiver() != null }
1122                     .groupBy {
1123                         val receiverType = it.getClassExtensionReceiver()!!
1124                         receiverType.links.filter { it.kind != NodeKind.ExternalLink}.firstOrNull() ?:
1125                             receiverType.links(NodeKind.ExternalLink).first()
1126                     }
1127 
1128             private fun List<DocumentationNode>.externalExtensions(kind: NodeKind) =
1129                 associateBy({ it }, { it.members(kind) })
1130                     .filterNot { (_, values) -> values.isEmpty() }
1131 
1132             val extensionFunctions =
1133                 node.members(NodeKind.ExternalClass).externalExtensions(NodeKind.Function) +
1134                         node.members(NodeKind.Function).groupedExtensions()
1135 
1136             val extensionProperties =
1137                 node.members(NodeKind.ExternalClass).externalExtensions(NodeKind.Property) +
1138                         node.members(NodeKind.Property).groupedExtensions()
1139 
1140             val functions = node.members(NodeKind.Function) - extensionFunctions.values.flatten()
1141             val properties = node.members(NodeKind.Property) - constants - extensionProperties.values.flatten()
1142 
1143         }
1144     }
1145 }
1146 
1147 class JavaLayoutHtmlFormatOutputBuilderFactoryImpl @Inject constructor(
1148     val uriProvider: JavaLayoutHtmlUriProvider,
1149     val languageService: LanguageService,
1150     val templateService: JavaLayoutHtmlTemplateService,
1151     val logger: DokkaLogger
1152 ) : JavaLayoutHtmlFormatOutputBuilderFactory {
createOutputBuildernull1153     override fun createOutputBuilder(output: Appendable, node: DocumentationNode): JavaLayoutHtmlFormatOutputBuilder {
1154         return createOutputBuilder(output, uriProvider.mainUri(node))
1155     }
1156 
createOutputBuildernull1157     override fun createOutputBuilder(output: Appendable, uri: URI): JavaLayoutHtmlFormatOutputBuilder {
1158         return JavaLayoutHtmlFormatOutputBuilder(output, languageService, uriProvider, templateService, logger, uri)
1159     }
1160 }
1161 
DocumentationNodenull1162 fun DocumentationNode.constantValue(): String? =
1163     detailOrNull(NodeKind.Value)?.name.takeIf {
1164         kind == NodeKind.Field || kind == NodeKind.Property || kind == NodeKind.CompanionObjectProperty
1165     }
1166 
1167 
1168 private val visibilityNames = setOf("public", "protected", "internal", "package-local", "private")
1169 
visibilitynull1170 fun DocumentationNode.visibility(): String =
1171         details(NodeKind.Modifier).firstOrNull { it.name in visibilityNames }?.name ?: ""
1172