blob: 115e3f76d7bfc6eb970aec3ec479506dbeaf8684 [file] [log] [blame]
package org.jetbrains.dokka.Formats
import com.google.inject.Inject
import com.google.inject.name.Named
import org.jetbrains.dokka.*
import org.jetbrains.dokka.Formats.JavaLayoutHtmlFormatOutputBuilder.Page
import org.jetbrains.dokka.NodeKind.Companion.classLike
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
import java.io.BufferedWriter
import java.io.File
import java.net.URI
class JavaLayoutHtmlFormatGenerator @Inject constructor(
@Named("outputDir") val root: File,
val packageListService: PackageListService,
val outputBuilderFactoryService: JavaLayoutHtmlFormatOutputBuilderFactory,
private val options: DocumentationOptions,
val logger: DokkaLogger,
@Named("outlineRoot") val outlineRoot: String
) : Generator, JavaLayoutHtmlUriProvider {
@set:Inject(optional = true)
var outlineFactoryService: JavaLayoutHtmlFormatOutlineFactoryService? = null
fun createOutputBuilderForNode(node: DocumentationNode, output: Appendable) = outputBuilderFactoryService.createOutputBuilder(output, node)
fun DocumentationNode.getOwnerOrReport() = owner ?: run {
error("Owner not found for $this")
}
override fun tryGetContainerUri(node: DocumentationNode): URI? {
return when (node.kind) {
NodeKind.Module -> URI("/").resolve(node.name + "/")
NodeKind.Package -> tryGetContainerUri(node.getOwnerOrReport())?.resolve(node.name.replace('.', '/') + '/')
in NodeKind.classLike -> tryGetContainerUri(node.getOwnerOrReport())?.resolve("${node.classNodeNameWithOuterClass()}.html")
else -> null
}
}
override fun tryGetMainUri(node: DocumentationNode): URI? {
return when (node.kind) {
NodeKind.Package -> tryGetContainerUri(node)?.resolve("package-summary.html")
in NodeKind.classLike -> tryGetContainerUri(node)?.resolve("#")
in NodeKind.memberLike -> {
val owner = if (node.owner?.kind != NodeKind.ExternalClass) node.owner else node.owner?.owner
if (owner!!.kind in classLike &&
(node.kind == NodeKind.CompanionObjectProperty || node.kind == NodeKind.CompanionObjectFunction) &&
owner.companion != null
) {
val signature = node.detail(NodeKind.Signature)
val originalFunction = owner.companion!!.members.first { it.detailOrNull(NodeKind.Signature)?.name == signature.name }
tryGetMainUri(owner.companion!!)?.resolveInPage(originalFunction)
} else {
tryGetMainUri(owner)?.resolveInPage(node)
}
}
NodeKind.TypeParameter, NodeKind.Parameter -> node.path.asReversed().drop(1).firstNotNullResult(this::tryGetMainUri)?.resolveInPage(node)
NodeKind.AllTypes -> outlineRootUri(node).resolve ("classes.html")
else -> null
}
}
override fun tryGetOutlineRootUri(node: DocumentationNode): URI? {
return when(node.kind) {
NodeKind.AllTypes -> tryGetContainerUri(node.getOwnerOrReport())
else -> tryGetContainerUri(node)
}?.resolve(outlineRoot)
}
fun URI.resolveInPage(node: DocumentationNode): URI = resolve("#${node.signatureForAnchor(logger).anchorEncoded()}")
fun buildClass(node: DocumentationNode, parentDir: File) {
val fileForClass = parentDir.resolve(node.classNodeNameWithOuterClass() + ".html")
fileForClass.bufferedWriter().use {
createOutputBuilderForNode(node, it).generatePage(Page.ClassPage(node))
}
for (memberClass in node.members.filter { it.kind in NodeKind.classLike }) {
buildClass(memberClass, parentDir)
}
}
fun buildPackage(node: DocumentationNode, parentDir: File) {
assert(node.kind == NodeKind.Package)
val members = node.members
val directoryForPackage = parentDir.resolve(node.name.replace('.', File.separatorChar))
directoryForPackage.mkdirsOrFail()
directoryForPackage.resolve("package-summary.html").bufferedWriter().use {
createOutputBuilderForNode(node, it).generatePage(Page.PackagePage(node))
}
members.filter { it.kind in NodeKind.classLike }.forEach {
buildClass(it, directoryForPackage)
}
}
fun buildClassIndex(node: DocumentationNode, parentDir: File) {
val file = parentDir.resolve("classes.html")
file.bufferedWriter().use {
createOutputBuilderForNode(node, it).generatePage(Page.ClassIndex(node))
}
}
fun buildPackageIndex(module: DocumentationNode, nodes: List<DocumentationNode>, parentDir: File) {
val file = parentDir.resolve("packages.html")
file.bufferedWriter().use {
val uri = outlineRootUri(module).resolve("packages.html")
outputBuilderFactoryService.createOutputBuilder(it, uri)
.generatePage(Page.PackageIndex(nodes))
}
}
override fun buildPages(nodes: Iterable<DocumentationNode>) {
val module = nodes.single()
val moduleRoot = root.resolve(module.name)
val packages = module.members.filter { it.kind == NodeKind.Package }
packages.forEach { buildPackage(it, moduleRoot) }
val outlineRootFile = moduleRoot.resolve(outlineRoot)
if (options.generateClassIndexPage) {
buildClassIndex(module.members.single { it.kind == NodeKind.AllTypes }, outlineRootFile)
}
if (options.generatePackageIndexPage) {
buildPackageIndex(module, packages, outlineRootFile)
}
}
override fun buildOutlines(nodes: Iterable<DocumentationNode>) {
val uriToWriter = mutableMapOf<URI, BufferedWriter>()
fun provideOutput(uri: URI): BufferedWriter {
val normalized = uri.normalize()
uriToWriter[normalized]?.let { return it }
val file = root.resolve(normalized.path.removePrefix("/"))
val writer = file.bufferedWriter()
uriToWriter[normalized] = writer
return writer
}
outlineFactoryService?.generateOutlines(::provideOutput, nodes)
uriToWriter.values.forEach { it.close() }
}
override fun buildSupportFiles() {}
override fun buildPackageList(nodes: Iterable<DocumentationNode>) {
nodes.filter { it.kind == NodeKind.Module }.forEach { module ->
val moduleRoot = root.resolve(module.name)
val packageListFile = moduleRoot.resolve("package-list")
packageListFile.writeText(packageListService.formatPackageList(module as DocumentationModule))
}
}
}
interface JavaLayoutHtmlFormatOutputBuilderFactory {
fun createOutputBuilder(output: Appendable, uri: URI): JavaLayoutHtmlFormatOutputBuilder
fun createOutputBuilder(output: Appendable, node: DocumentationNode): JavaLayoutHtmlFormatOutputBuilder
}