xref: /aosp_15_r20/external/dokka/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlGenerator.kt (revision 1b2d298c530bf0473cc943e8414a5ff577a994bc)

<lambda>null1 package org.jetbrains.dokka.Formats
2 
3 import com.google.inject.Inject
4 import com.google.inject.name.Named
5 import org.jetbrains.dokka.*
6 import org.jetbrains.dokka.Formats.JavaLayoutHtmlFormatOutputBuilder.Page
7 import org.jetbrains.dokka.NodeKind.Companion.classLike
8 import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
9 import java.io.BufferedWriter
10 import java.io.File
11 import java.net.URI
12 
13 class JavaLayoutHtmlFormatGenerator @Inject constructor(
14         @Named("outputDir") val root: File,
15         val packageListService: PackageListService,
16         val outputBuilderFactoryService: JavaLayoutHtmlFormatOutputBuilderFactory,
17         private val options: DocumentationOptions,
18         val logger: DokkaLogger,
19         @Named("outlineRoot") val outlineRoot: String
20 ) : Generator, JavaLayoutHtmlUriProvider {
21 
22     @set:Inject(optional = true)
23     var outlineFactoryService: JavaLayoutHtmlFormatOutlineFactoryService? = null
24 
25     fun createOutputBuilderForNode(node: DocumentationNode, output: Appendable) = outputBuilderFactoryService.createOutputBuilder(output, node)
26 
27     fun DocumentationNode.getOwnerOrReport() = owner ?: run {
28         error("Owner not found for $this")
29     }
30 
31     override fun tryGetContainerUri(node: DocumentationNode): URI? {
32         return when (node.kind) {
33             NodeKind.Module -> URI("/").resolve(node.name + "/")
34             NodeKind.Package -> tryGetContainerUri(node.getOwnerOrReport())?.resolve(node.name.replace('.', '/') + '/')
35             NodeKind.GroupNode -> tryGetContainerUri(node.getOwnerOrReport())
36             in NodeKind.classLike -> tryGetContainerUri(node.getOwnerOrReport())?.resolve("${node.classNodeNameWithOuterClass()}.html")
37             else -> null
38         }
39     }
40 
41     override fun tryGetMainUri(node: DocumentationNode): URI? {
42         return when (node.kind) {
43             NodeKind.Package -> tryGetContainerUri(node)?.resolve("package-summary.html")
44             in NodeKind.classLike -> tryGetContainerUri(node)?.resolve("#")
45             in NodeKind.memberLike -> {
46                 val owner = if (node.owner?.kind != NodeKind.ExternalClass) node.owner else node.owner?.owner
47                 if (owner!!.kind in classLike &&
48                         (node.kind == NodeKind.CompanionObjectProperty || node.kind == NodeKind.CompanionObjectFunction) &&
49                         owner.companion != null
50                 ) {
51                     val signature = node.detail(NodeKind.Signature)
52                     val originalFunction = owner.companion!!.members.first { it.detailOrNull(NodeKind.Signature)?.name == signature.name }
53                     tryGetMainUri(owner.companion!!)?.resolveInPage(originalFunction)
54                 } else {
55                     tryGetMainUri(owner)?.resolveInPage(node)
56                 }
57             }
58             NodeKind.TypeParameter, NodeKind.Parameter -> node.path.asReversed().drop(1).firstNotNullResult(this::tryGetMainUri)?.resolveInPage(node)
59             NodeKind.AllTypes -> outlineRootUri(node).resolve ("classes.html")
60             else -> null
61         }
62     }
63 
64     override fun tryGetOutlineRootUri(node: DocumentationNode): URI? {
65         return when(node.kind) {
66             NodeKind.AllTypes -> tryGetContainerUri(node.getOwnerOrReport())
67             else -> tryGetContainerUri(node)
68         }?.resolve(outlineRoot)
69     }
70 
71     fun URI.resolveInPage(node: DocumentationNode): URI = resolve("#${node.signatureForAnchor(logger).anchorEncoded()}")
72 
73     fun buildClass(node: DocumentationNode, parentDir: File) {
74         val fileForClass = parentDir.resolve(node.classNodeNameWithOuterClass() + ".html")
75         fileForClass.bufferedWriter().use {
76             createOutputBuilderForNode(node, it).generatePage(Page.ClassPage(node))
77         }
78         for (memberClass in node.members.filter { it.kind in NodeKind.classLike }) {
79             buildClass(memberClass, parentDir)
80         }
81     }
82 
83     fun buildPackage(node: DocumentationNode, parentDir: File) {
84         assert(node.kind == NodeKind.Package)
85         var members = node.members
86         val directoryForPackage = parentDir.resolve(node.name.replace('.', File.separatorChar))
87         directoryForPackage.mkdirsOrFail()
88 
89         directoryForPackage.resolve("package-summary.html").bufferedWriter().use {
90             createOutputBuilderForNode(node, it).generatePage(Page.PackagePage(node))
91         }
92 
93         members.filter { it.kind == NodeKind.GroupNode }.forEach {
94             members += it.members
95         }
96         members.filter { it.kind in NodeKind.classLike }.forEach {
97             buildClass(it, directoryForPackage)
98         }
99     }
100 
101     fun buildClassIndex(node: DocumentationNode, parentDir: File) {
102         val file = parentDir.resolve("classes.html")
103         file.bufferedWriter().use {
104             createOutputBuilderForNode(node, it).generatePage(Page.ClassIndex(node))
105         }
106     }
107 
108     fun buildPackageIndex(module: DocumentationNode, nodes: List<DocumentationNode>, parentDir: File) {
109         val file = parentDir.resolve("packages.html")
110         file.bufferedWriter().use {
111             val uri = outlineRootUri(module).resolve("packages.html")
112             outputBuilderFactoryService.createOutputBuilder(it, uri)
113                 .generatePage(Page.PackageIndex(nodes))
114         }
115     }
116 
117     override fun buildPages(nodes: Iterable<DocumentationNode>) {
118         val module = nodes.single()
119 
120         val moduleRoot = root.resolve(module.name)
121         val packages = module.members.filter { it.kind == NodeKind.Package }
122         packages.forEach { buildPackage(it, moduleRoot) }
123         val outlineRootFile = moduleRoot.resolve(outlineRoot)
124         if (options.generateClassIndexPage) {
125             buildClassIndex(module.members.single { it.kind == NodeKind.AllTypes }, outlineRootFile)
126         }
127 
128         if (options.generatePackageIndexPage) {
129             buildPackageIndex(module, packages, outlineRootFile)
130         }
131     }
132 
133     override fun buildOutlines(nodes: Iterable<DocumentationNode>) {
134         val uriToWriter = mutableMapOf<URI, BufferedWriter>()
135 
136         fun provideOutput(uri: URI): BufferedWriter {
137             val normalized = uri.normalize()
138             uriToWriter[normalized]?.let { return it }
139             val file = root.resolve(normalized.path.removePrefix("/"))
140             file.parentFile.mkdirsOrFail()
141             val writer = file.bufferedWriter()
142             uriToWriter[normalized] = writer
143             return writer
144         }
145 
146         outlineFactoryService?.generateOutlines(::provideOutput, nodes)
147 
148         uriToWriter.values.forEach { it.close() }
149     }
150 
151     override fun buildSupportFiles() {}
152 
153     override fun buildPackageList(nodes: Iterable<DocumentationNode>) {
154         nodes.filter { it.kind == NodeKind.Module }.forEach { module ->
155             val moduleRoot = root.resolve(module.name)
156             val packageListFile = moduleRoot.resolve("package-list")
157             packageListFile.writeText(packageListService.formatPackageList(module as DocumentationModule))
158         }
159     }
160 }
161 
162 interface JavaLayoutHtmlFormatOutputBuilderFactory {
createOutputBuildernull163     fun createOutputBuilder(output: Appendable, uri: URI): JavaLayoutHtmlFormatOutputBuilder
164     fun createOutputBuilder(output: Appendable, node: DocumentationNode): JavaLayoutHtmlFormatOutputBuilder
165 }
166