<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