• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<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             summaryNodeGroup(page.interfaces, "Interfaces", headerAsRow = false) { classLikeRow(it) }
396             summaryNodeGroup(page.classes, "Classes", headerAsRow = false) { classLikeRow(it) }
397             summaryNodeGroup(page.exceptions, "Exceptions", headerAsRow = false) { classLikeRow(it) }
398             summaryNodeGroup(page.typeAliases, "Type-aliases", headerAsRow = false) { classLikeRow(it) }
399             summaryNodeGroup(page.annotations, "Annotations", headerAsRow = false) { classLikeRow(it) }
400             summaryNodeGroup(page.enums, "Enums", headerAsRow = false) { classLikeRow(it) }
401 
402             summaryNodeGroup(
403                 page.constants,
404                 "Top-level constants summary",
405                 headerAsRow = false
406             ) {
407                 propertyLikeSummaryRow(it)
408             }
409 
410             summaryNodeGroup(
411                 page.functions,
412                 "Top-level functions summary",
413                 headerAsRow = false
414             ) {
415                 functionLikeSummaryRow(it)
416             }
417 
418             summaryNodeGroup(
419                 page.properties,
420                 "Top-level properties summary",
421                 headerAsRow = false
422             ) {
423                 propertyLikeSummaryRow(it)
424             }
425 
426             summaryNodeGroup(
427                 page.extensionFunctions.entries,
428                 "Extension functions summary",
429                 headerAsRow = false
430             ) {
431                 extensionByReceiverRow(it) {
432                     functionLikeSummaryRow(it)
433                 }
434             }
435 
436             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         summaryNodeGroup(
532             nestedClasses,
533             "Nested classes",
534             headerAsRow = true
535         ) {
536             nestedClassSummaryRow(it)
537         }
538 
539         summaryNodeGroup(enumValues, "Enum values") {
540             propertyLikeSummaryRow(it)
541         }
542 
543         summaryNodeGroup(constants, "Constants") { propertyLikeSummaryRow(it) }
544 
545         constructors.forEach { (visibility, group) ->
546             summaryNodeGroup(
547                     group,
548                     "${visibility.capitalize()} constructors",
549                     headerAsRow = true
550             ) {
551                 functionLikeSummaryRow(it)
552             }
553         }
554 
555         functions.forEach { (visibility, group) ->
556             summaryNodeGroup(
557                     group,
558                     "${visibility.capitalize()} functions",
559                     headerAsRow = true
560             ) {
561                 functionLikeSummaryRow(it)
562             }
563         }
564 
565         summaryNodeGroup(
566             companionFunctions,
567             "Companion functions",
568             headerAsRow = true
569         ) {
570             functionLikeSummaryRow(it)
571         }
572         summaryNodeGroup(
573             inheritedFunctionsByReceiver.entries,
574             "Inherited functions",
575             headerAsRow = true
576         ) {
577             inheritRow(it) {
578                 functionLikeSummaryRow(it)
579             }
580         }
581         summaryNodeGroup(
582             extensionFunctions.entries,
583             "Extension functions",
584             headerAsRow = true
585         ) {
586             extensionRow(it) {
587                 functionLikeSummaryRow(it)
588             }
589         }
590         summaryNodeGroup(
591             inheritedExtensionFunctions.entries,
592             "Inherited extension functions",
593             headerAsRow = true
594         ) {
595             extensionRow(it) {
596                 functionLikeSummaryRow(it)
597             }
598         }
599 
600 
601         summaryNodeGroup(properties, "Properties", headerAsRow = true) { propertyLikeSummaryRow(it) }
602         summaryNodeGroup(
603             companionProperties,
604             "Companion properties",
605             headerAsRow = true
606         ) {
607             propertyLikeSummaryRow(it)
608         }
609 
610         summaryNodeGroup(
611             inheritedPropertiesByReceiver.entries,
612             "Inherited properties",
613             headerAsRow = true
614         ) {
615             inheritRow(it) {
616                 propertyLikeSummaryRow(it)
617             }
618         }
619         summaryNodeGroup(
620             extensionProperties.entries,
621             "Extension properties",
622             headerAsRow = true
623         ) {
624             extensionRow(it) {
625                 propertyLikeSummaryRow(it)
626             }
627         }
628         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 deprecatedLevelExists = node.deprecatedLevel.name.isNotEmpty()
861         if (apiLevelExists || deprecatedLevelExists) {
862             div(classes = "api-level") {
863                 if (apiLevelExists) {
864                     +"Added in "
865                     a(href = "https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels") {
866                         +"API level ${node.apiLevel.name}"
867                     }
868                     if (deprecatedLevelExists) {
869                         br
870                     }
871                 }
872                 if (deprecatedLevelExists) {
873                     +"Deprecated in "
874                     a(href = "https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels") {
875                         +"API level ${node.deprecatedLevel.name}"
876                     }
877                 }
878             }
879         }
880     }
881 
882     protected open fun formatDeprecationOrNull(
883         node: DocumentationNode,
884         prefix: Boolean = false,
885         emphasis: Boolean = true): ContentNode? {
886         val deprecated = node.deprecation
887         deprecated?.let {
888             return ContentParagraph("caution").apply {
889                 if (prefix) {
890                     append(ContentStrong().apply { text(
891                         if (deprecated.content.children.size == 0) "Deprecated."
892                         else "Deprecated: "
893                     ) })
894                 }
895                 val em = if (emphasis) ContentEmphasis() else ContentBlock()
896                 for (child in deprecated.content.children) {
897                     em.append(child)
898                 }
899                 append(em)
900             }
901         }
902         return null
903     }
904 
905     protected open fun FlowContent.section(name: String, sectionParts: List<ContentSection>) {
906         when (name) {
907             ContentTags.SeeAlso -> seeAlsoSection(sectionParts.map { it.children.flatMap { (it as? ContentParagraph)?.children ?: listOf(it) } })
908             else -> regularSection(name, sectionParts)
909         }
910     }
911 
912     protected open fun FlowContent.sections(content: Content) {
913         val sectionsByTag = content.sections.groupByTo(mutableMapOf()) { it.tag }
914 
915         val seeAlso = sectionsByTag.remove(ContentTags.SeeAlso)
916 
917         for ((name, entries) in sectionsByTag) {
918             section(name, entries)
919         }
920 
921         seeAlso?.let { section(ContentTags.SeeAlso, it) }
922     }
923 
924     protected open fun FlowContent.fullMemberDocs(node: DocumentationNode, uriNode: DocumentationNode) {
925         div {
926             id = node.signatureForAnchor(logger)
927             h3 { +node.name }
928             pre { renderedSignature(node, FULL) }
929             deprecationWarningToMarkup(node, prefix = true)
930             nodeContent(node)
931             node.constantValue()?.let { value ->
932                 pre {
933                     +"Value: "
934                     code { +value }
935                 }
936             }
937 
938             sections(node.content)
939         }
940     }
941 
942     protected open fun FlowContent.fullMemberDocs(node: DocumentationNode) {
943         fullMemberDocs(node, node)
944     }
945 
946     sealed class Page {
947         class PackageIndex(packages: List<DocumentationNode>) : Page() {
948             init {
949                 assert(packages.all { it.kind == NodeKind.Package })
950             }
951 
952             val packages = packages.sortedBy { it.name }
953         }
954 
955         class ClassIndex(allTypesNode: DocumentationNode) : Page() {
956             init {
957                 assert(allTypesNode.kind == NodeKind.AllTypes)
958             }
959 
960             // Wide-collect all nested classes
961             val classes: List<DocumentationNode> =
962                 generateSequence(listOf(allTypesNode)) { nodes ->
963                     nodes
964                         .flatMap { it.members.filter { it.kind in NodeKind.classLike } }
965                         .takeUnless { it.isEmpty() }
966                 }.drop(1)
967                     .flatten()
968                     .sortedBy { it.classNodeNameWithOuterClass().toLowerCase() }
969                     .toList()
970 
971 
972             // Group all classes by it's first letter and sort
973             val classesByFirstLetter =
974                 classes
975                     .groupBy {
976                         it.classNodeNameWithOuterClass().first().toString()
977                     }
978                     .entries
979                     .sortedBy { (letter) ->
980                         val x = letter.toLowerCase()
981                         x
982                     }
983         }
984 
985         class ClassPage(val node: DocumentationNode) : Page() {
986 
987             init {
988                 assert(node.kind in NodeKind.classLike)
989             }
990 
991             val superclasses = (sequenceOf(node) + node.superclassTypeSequence).toList().asReversed()
992 
993             val enumValues = node.members(NodeKind.EnumItem).sortedBy { it.name }
994 
995             val directInheritors: List<DocumentationNode>
996             val indirectInheritors: List<DocumentationNode>
997 
998             init {
999                 // Wide-collect all inheritors
1000                 val inheritors = generateSequence(node.inheritors) { inheritors ->
1001                     inheritors
1002                         .flatMap { it.inheritors }
1003                         .takeUnless { it.isEmpty() }
1004                 }
1005                 directInheritors = inheritors.first().sortedBy { it.classNodeNameWithOuterClass() }
1006                 indirectInheritors = inheritors.drop(1).flatten().toList().sortedBy { it.classNodeNameWithOuterClass() }
1007             }
1008 
1009             val isCompanion = node.details(NodeKind.Modifier).any { it.name == "companion" }
1010             val hasMeaningfulCompanion = !isCompanion && node.companion != null
1011 
1012             private fun DocumentationNode.thisTypeExtension() =
1013                 detail(NodeKind.Receiver).detail(NodeKind.Type).links.any { it == node }
1014 
1015             val functionKind = if (!isCompanion) NodeKind.Function else NodeKind.CompanionObjectFunction
1016             val propertyKind = if (!isCompanion) NodeKind.Property else NodeKind.CompanionObjectProperty
1017 
1018             private fun DocumentationNode.isFunction() = kind == functionKind
1019             private fun DocumentationNode.isProperty() = kind == propertyKind
1020 
1021 
1022             val nestedClasses = node.members.filter { it.kind in NodeKind.classLike } - enumValues
1023 
1024             val attributes = node.attributes
1025 
1026             val inheritedAttributes =
1027                     node.superclassTypeSequence
1028                             .toList()
1029                             .sortedBy { it.name }
1030                             .flatMap { it.typeDeclarationClass?.attributes.orEmpty() }
1031                             .distinctBy { it.attributeRef!!.name }
1032                             .groupBy { it.owner!! }
1033 
1034             val allInheritedMembers = node.allInheritedMembers
1035             val constants = node.members.filter { it.constantValue() != null }
1036             val inheritedConstants = allInheritedMembers.filter { it.constantValue() != null }.groupBy { it.owner!! }
1037 
1038 
1039             fun compareVisibilities(a: String, b: String): Int {
1040                 return visibilityNames.indexOf(a) - visibilityNames.indexOf(b)
1041             }
1042 
1043             fun Collection<DocumentationNode>.groupByVisibility() =
1044                     groupBy { it.visibility() }.toSortedMap(Comparator { a, b -> compareVisibilities(a, b) })
1045 
1046 
1047             val constructors = node.members(NodeKind.Constructor).groupByVisibility()
1048             val functions = node.members(functionKind).groupByVisibility()
1049             val fields = (node.members(NodeKind.Field) - constants).groupByVisibility()
1050             val properties = node.members(propertyKind) - constants
1051             val inheritedFunctionsByReceiver = allInheritedMembers.filter { it.kind == functionKind }.groupBy { it.owner!! }
1052             val inheritedPropertiesByReceiver =
1053                 allInheritedMembers.filter {
1054                     it.kind == propertyKind && it.constantValue() == null
1055                 }.groupBy { it.owner!! }
1056 
1057             val inheritedFieldsByReceiver =
1058                 allInheritedMembers.filter {
1059                     it == NodeKind.Field && it.constantValue() != null
1060                 }.groupBy { it.owner!! }
1061 
1062             val originalExtensions = if (!isCompanion) node.extensions else node.owner!!.extensions
1063 
1064             val extensionFunctions: Map<DocumentationNode, List<DocumentationNode>>
1065             val extensionProperties: Map<DocumentationNode, List<DocumentationNode>>
1066             val inheritedExtensionFunctions: Map<DocumentationNode, List<DocumentationNode>>
1067             val inheritedExtensionProperties: Map<DocumentationNode, List<DocumentationNode>>
1068 
1069             init {
1070                 val (extensions, inheritedExtensions) = originalExtensions.partition { it.thisTypeExtension() }
1071                 extensionFunctions = extensions.filter { it.isFunction() }.sortedBy { it.name }.groupBy { it.owner!! }
1072                 extensionProperties = extensions.filter { it.isProperty() }.sortedBy { it.name }.groupBy { it.owner!! }
1073                 inheritedExtensionFunctions =
1074                         inheritedExtensions.filter { it.isFunction() }.sortedBy { it.name }.groupBy { it.owner!! }
1075                 inheritedExtensionProperties =
1076                         inheritedExtensions.filter { it.isProperty() }.sortedBy { it.name }.groupBy { it.owner!! }
1077             }
1078 
1079             val companionFunctions = node.members(NodeKind.CompanionObjectFunction).takeUnless { isCompanion }.orEmpty()
1080             val companionProperties =
1081                 node.members(NodeKind.CompanionObjectProperty).takeUnless { isCompanion }.orEmpty() - constants
1082 
1083 
1084         }
1085 
1086         class PackagePage(val node: DocumentationNode) : Page() {
1087 
1088             init {
1089                 assert(node.kind == NodeKind.Package)
1090             }
1091 
1092             val interfaces = node.members(NodeKind.Interface) +
1093                     node.members(NodeKind.Class).flatMap { it.members(NodeKind.Interface) }
1094             val classes = node.members(NodeKind.Class)
1095             val exceptions = node.members(NodeKind.Exception)
1096             val typeAliases = node.members(NodeKind.TypeAlias)
1097             val annotations = node.members(NodeKind.AnnotationClass)
1098             val enums = node.members(NodeKind.Enum)
1099 
1100             val constants = node.members(NodeKind.Property).filter { it.constantValue() != null }
1101 
1102 
1103             private fun DocumentationNode.getClassExtensionReceiver() =
1104                 detailOrNull(NodeKind.Receiver)?.detailOrNull(NodeKind.Type)?.takeIf {
1105                     it.links.any { it.kind == NodeKind.ExternalLink || it.kind in NodeKind.classLike }
1106                 }
1107 
1108             private fun List<DocumentationNode>.groupedExtensions() =
1109                 filter { it.getClassExtensionReceiver() != null }
1110                     .groupBy {
1111                         val receiverType = it.getClassExtensionReceiver()!!
1112                         receiverType.links.filter { it.kind != NodeKind.ExternalLink}.firstOrNull() ?:
1113                             receiverType.links(NodeKind.ExternalLink).first()
1114                     }
1115 
1116             private fun List<DocumentationNode>.externalExtensions(kind: NodeKind) =
1117                 associateBy({ it }, { it.members(kind) })
1118                     .filterNot { (_, values) -> values.isEmpty() }
1119 
1120             val extensionFunctions =
1121                 node.members(NodeKind.ExternalClass).externalExtensions(NodeKind.Function) +
1122                         node.members(NodeKind.Function).groupedExtensions()
1123 
1124             val extensionProperties =
1125                 node.members(NodeKind.ExternalClass).externalExtensions(NodeKind.Property) +
1126                         node.members(NodeKind.Property).groupedExtensions()
1127 
1128             val functions = node.members(NodeKind.Function) - extensionFunctions.values.flatten()
1129             val properties = node.members(NodeKind.Property) - constants - extensionProperties.values.flatten()
1130 
1131         }
1132     }
1133 }
1134 
1135 class JavaLayoutHtmlFormatOutputBuilderFactoryImpl @Inject constructor(
1136     val uriProvider: JavaLayoutHtmlUriProvider,
1137     val languageService: LanguageService,
1138     val templateService: JavaLayoutHtmlTemplateService,
1139     val logger: DokkaLogger
1140 ) : JavaLayoutHtmlFormatOutputBuilderFactory {
createOutputBuildernull1141     override fun createOutputBuilder(output: Appendable, node: DocumentationNode): JavaLayoutHtmlFormatOutputBuilder {
1142         return createOutputBuilder(output, uriProvider.mainUri(node))
1143     }
1144 
createOutputBuildernull1145     override fun createOutputBuilder(output: Appendable, uri: URI): JavaLayoutHtmlFormatOutputBuilder {
1146         return JavaLayoutHtmlFormatOutputBuilder(output, languageService, uriProvider, templateService, logger, uri)
1147     }
1148 }
1149 
DocumentationNodenull1150 fun DocumentationNode.constantValue(): String? =
1151     detailOrNull(NodeKind.Value)?.name.takeIf {
1152         kind == NodeKind.Field || kind == NodeKind.Property || kind == NodeKind.CompanionObjectProperty
1153     }
1154 
1155 
1156 private val visibilityNames = setOf("public", "protected", "internal", "package-local", "private")
1157 
visibilitynull1158 fun DocumentationNode.visibility(): String =
1159         details(NodeKind.Modifier).firstOrNull { it.name in visibilityNames }?.name ?: ""