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