aboutsummaryrefslogtreecommitdiff
path: root/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka
diff options
context:
space:
mode:
authorBłażej Kardyś <bkardys@virtuslab.com>2020-07-29 19:34:12 +0200
committerBłażej Kardyś <bkardys@virtuslab.com>2020-08-04 00:47:36 +0200
commita673f77482fea4ba89f9fdf3a7544955cf8b8226 (patch)
tree2942bdbfc87168f96893a2b9d6da2f9fa649c94e /plugins/javadoc/src/main/kotlin/org/jetbrains/dokka
parent401e657eff30df5cdb4b0415b6218167ea08916b (diff)
downloaddokka-a673f77482fea4ba89f9fdf3a7544955cf8b8226.tar.gz
dokka-a673f77482fea4ba89f9fdf3a7544955cf8b8226.tar.bz2
dokka-a673f77482fea4ba89f9fdf3a7544955cf8b8226.zip
Javadoc plugin package name fix
Diffstat (limited to 'plugins/javadoc/src/main/kotlin/org/jetbrains/dokka')
-rw-r--r--plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocDocumentableToPageTranslator.kt18
-rw-r--r--plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPageCreator.kt238
-rw-r--r--plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPlugin.kt53
-rw-r--r--plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProvider.kt129
-rw-r--r--plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProviderFactory.kt11
-rw-r--r--plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocContentNodes.kt173
-rw-r--r--plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocIndexExtra.kt10
-rw-r--r--plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocPageNodes.kt436
-rw-r--r--plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/htmlPreprocessors.kt68
-rw-r--r--plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/pages.kt17
-rw-r--r--plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToHtmlTranslator.kt63
-rw-r--r--plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToTemplateMapTranslator.kt222
-rw-r--r--plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/KorteJavadocRenderer.kt190
-rw-r--r--plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/SearchScriptsCreator.kt261
-rw-r--r--plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/signatures/JavadocSignatureProvider.kt201
-rw-r--r--plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/translators/documentables/JavadocPageContentBuilder.kt80
-rw-r--r--plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/utils.kt8
17 files changed, 2178 insertions, 0 deletions
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocDocumentableToPageTranslator.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocDocumentableToPageTranslator.kt
new file mode 100644
index 00000000..f8723f33
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocDocumentableToPageTranslator.kt
@@ -0,0 +1,18 @@
+package org.jetbrains.dokka.javadoc
+
+import org.jetbrains.dokka.base.signatures.SignatureProvider
+import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter
+import org.jetbrains.dokka.model.DModule
+import org.jetbrains.dokka.pages.ModulePageNode
+import org.jetbrains.dokka.pages.RootPageNode
+import org.jetbrains.dokka.transformers.documentation.DocumentableToPageTranslator
+import org.jetbrains.dokka.utilities.DokkaLogger
+
+class JavadocDocumentableToPageTranslator(
+ private val commentsToContentConverter: CommentsToContentConverter,
+ private val signatureProvider: SignatureProvider,
+ private val logger: DokkaLogger
+) : DocumentableToPageTranslator {
+ override fun invoke(module: DModule): RootPageNode =
+ JavadocPageCreator(commentsToContentConverter, signatureProvider, logger).pageForModule(module)
+} \ No newline at end of file
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPageCreator.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPageCreator.kt
new file mode 100644
index 00000000..8b86fab0
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPageCreator.kt
@@ -0,0 +1,238 @@
+package org.jetbrains.dokka.javadoc
+
+import org.jetbrains.dokka.javadoc.pages.*
+import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.base.signatures.SignatureProvider
+import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter
+import org.jetbrains.dokka.base.transformers.pages.comments.DocTagToContentConverter
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.doc.Description
+import org.jetbrains.dokka.model.doc.Index
+import org.jetbrains.dokka.model.doc.Param
+import org.jetbrains.dokka.model.doc.TagWrapper
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.model.properties.WithExtraProperties
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.utilities.DokkaLogger
+import kotlin.reflect.KClass
+
+open class JavadocPageCreator(
+ commentsToContentConverter: CommentsToContentConverter,
+ private val signatureProvider: SignatureProvider,
+ val logger: DokkaLogger
+) {
+
+ fun pageForModule(m: DModule): JavadocModulePageNode =
+ JavadocModulePageNode(
+ name = m.name.ifEmpty { "root" },
+ content = contentForModule(m),
+ children = m.packages.map { pageForPackage(it) },
+ dri = setOf(m.dri)
+ )
+
+ fun pageForPackage(p: DPackage) =
+ JavadocPackagePageNode(p.name, contentForPackage(p), setOf(p.dri), p,
+ p.classlikes.mapNotNull { pageForClasslike(it) } // TODO: nested classlikes
+ )
+
+ fun pageForClasslike(c: DClasslike): JavadocClasslikePageNode? =
+ c.highestJvmSourceSet?.let { jvm ->
+ JavadocClasslikePageNode(
+ name = c.name.orEmpty(),
+ content = contentForClasslike(c),
+ dri = setOf(c.dri),
+ signature = signatureForNode(c, jvm),
+ description = c.descriptionToContentNodes(),
+ constructors = (c as? WithConstructors)?.constructors?.mapNotNull { it.toJavadocFunction() }.orEmpty(),
+ methods = c.functions.mapNotNull { it.toJavadocFunction() },
+ entries = (c as? DEnum)?.entries?.map {
+ JavadocEntryNode(
+ it.dri,
+ it.name,
+ signatureForNode(it, jvm),
+ it.descriptionToContentNodes(jvm),
+ PropertyContainer.withAll(it.indexesInDocumentation())
+ )
+ }.orEmpty(),
+ classlikes = c.classlikes.mapNotNull { pageForClasslike(it) },
+ properties = c.properties.map {
+ JavadocPropertyNode(
+ it.dri,
+ it.name,
+ signatureForNode(it, jvm),
+ it.descriptionToContentNodes(jvm),
+ PropertyContainer.withAll(it.indexesInDocumentation())
+ )
+ },
+ documentable = c,
+ extra = ((c as? WithExtraProperties<Documentable>)?.extra ?: PropertyContainer.empty()) + c.indexesInDocumentation()
+ )
+ }
+
+ private fun contentForModule(m: DModule): JavadocContentNode =
+ JavadocContentGroup(
+ setOf(m.dri),
+ JavadocContentKind.OverviewSummary,
+ m.jvmSourceSets.toSet()
+ ) {
+ title(m.name, m.brief(), "0.0.1", dri = setOf(m.dri), kind = ContentKind.Main)
+ leafList(setOf(m.dri),
+ ContentKind.Packages, JavadocList(
+ "Packages", "Package",
+ m.packages.sortedBy { it.name }.map { p ->
+ RowJavadocListEntry(
+ LinkJavadocListEntry(p.name, setOf(p.dri), JavadocContentKind.PackageSummary, sourceSets),
+ p.brief()
+ )
+ }
+ ))
+ }
+
+ private fun contentForPackage(p: DPackage): JavadocContentNode =
+ JavadocContentGroup(
+ setOf(p.dri),
+ JavadocContentKind.PackageSummary,
+ p.jvmSourceSets.toSet()
+ ) {
+ title(p.name, p.brief(), "0.0.1", dri = setOf(p.dri), kind = ContentKind.Packages)
+ val rootList = p.classlikes.groupBy { it::class }.map { (key, value) ->
+ JavadocList(key.tabTitle, key.colTitle, value.map { c ->
+ RowJavadocListEntry(
+ LinkJavadocListEntry(c.name ?: "", setOf(c.dri), JavadocContentKind.Class, sourceSets),
+ c.brief()
+ )
+ })
+ }
+ rootList(setOf(p.dri), JavadocContentKind.Class, rootList)
+ }
+
+ private val KClass<out DClasslike>.colTitle: String
+ get() = when(this) {
+ DClass::class -> "Class"
+ DObject::class -> "Object"
+ DAnnotation::class -> "Annotation"
+ DEnum::class -> "Enum"
+ DInterface::class -> "Interface"
+ else -> ""
+ }
+
+ private val KClass<out DClasslike>.tabTitle: String
+ get() = "$colTitle Summary"
+
+ private fun contentForClasslike(c: DClasslike): JavadocContentNode =
+ JavadocContentGroup(
+ setOf(c.dri),
+ JavadocContentKind.Class,
+ c.jvmSourceSets.toSet()
+ ) {
+ title(
+ c.name.orEmpty(),
+ c.brief(),
+ "0.0.1",
+ parent = c.dri.packageName,
+ dri = setOf(c.dri),
+ kind = JavadocContentKind.Class
+ )
+ }
+
+ private fun DFunction.toJavadocFunction() = highestJvmSourceSet?.let { jvm ->
+ JavadocFunctionNode(
+ name = name,
+ dri = dri,
+ signature = signatureForNode(this, jvm),
+ brief = brief(jvm),
+ parameters = parameters.mapNotNull {
+ val signature = signatureForNode(it, jvm)
+ signature.modifiers?.let { type ->
+ JavadocParameterNode(
+ name = it.name.orEmpty(),
+ type = type,
+ description = it.brief(),
+ typeBound = it.type,
+ dri = it.dri,
+ extra = PropertyContainer.withAll(it.indexesInDocumentation())
+ )
+ }
+ },
+ extra = extra + indexesInDocumentation()
+ )
+ }
+
+ private val Documentable.jvmSourceSets
+ get() = sourceSets.filter { it.analysisPlatform == Platform.jvm }
+
+ private val Documentable.highestJvmSourceSet
+ get() = jvmSourceSets.let { sources ->
+ sources.firstOrNull { it != expectPresentInSet } ?: sources.firstOrNull()
+ }
+
+ private val firstSentenceRegex = Regex("^((?:[^.?!]|[.!?](?!\\s))*[.!?])")
+
+ private inline fun <reified T : TagWrapper> Documentable.findNodeInDocumentation(sourceSetData: DokkaSourceSet?): T? =
+ documentation[sourceSetData]?.firstChildOfTypeOrNull<T>()
+
+ private fun Documentable.descriptionToContentNodes(sourceSet: DokkaSourceSet? = highestJvmSourceSet) =
+ contentNodesFromType<Description>(sourceSet)
+
+ private fun DParameter.paramsToContentNodes(sourceSet: DokkaSourceSet? = highestJvmSourceSet) =
+ contentNodesFromType<Param>(sourceSet)
+
+ private inline fun <reified T : TagWrapper> Documentable.contentNodesFromType(sourceSet: DokkaSourceSet?) =
+ findNodeInDocumentation<T>(sourceSet)?.let {
+ DocTagToContentConverter.buildContent(
+ it.root,
+ DCI(setOf(dri), JavadocContentKind.OverviewSummary),
+ sourceSets.toSet()
+ )
+ }.orEmpty()
+
+ fun List<ContentNode>.nodeForJvm(jvm: DokkaSourceSet): ContentNode =
+ first { it.sourceSets.contains(jvm) }
+
+ private fun Documentable.brief(sourceSet: DokkaSourceSet? = highestJvmSourceSet): List<ContentNode> =
+ briefFromContentNodes(descriptionToContentNodes(sourceSet))
+
+ private fun briefFromContentNodes(description: List<ContentNode>): List<ContentNode> {
+ val contents = mutableListOf<ContentNode>()
+ for (node in description) {
+ if (node is ContentText && firstSentenceRegex.containsMatchIn(node.text)) {
+ contents.add(node.copy(text = firstSentenceRegex.find(node.text)?.value.orEmpty()))
+ break
+ } else {
+ contents.add(node)
+ }
+ }
+ return contents
+ }
+
+ private fun DParameter.brief(sourceSet: DokkaSourceSet? = highestJvmSourceSet): List<ContentNode> =
+ briefFromContentNodes(paramsToContentNodes(sourceSet).dropWhile { it is ContentDRILink })
+
+ private fun ContentNode.asJavadocNode(): JavadocSignatureContentNode =
+ (this as ContentGroup).firstChildOfTypeOrNull<JavadocSignatureContentNode>()
+ ?: throw IllegalStateException("No content for javadoc signature found")
+
+ private fun signatureForNode(documentable: Documentable, sourceSet: DokkaSourceSet): JavadocSignatureContentNode =
+ signatureProvider.signature(documentable).nodeForJvm(sourceSet).asJavadocNode()
+
+ private fun Documentable.indexesInDocumentation(): JavadocIndexExtra {
+ val indexes = documentation[highestJvmSourceSet]?.withDescendants()?.filterIsInstance<Index>()?.toList().orEmpty()
+ return JavadocIndexExtra(
+ indexes.map {
+ ContentGroup(
+ children = DocTagToContentConverter.buildContent(
+ it,
+ DCI(setOf(dri), JavadocContentKind.OverviewSummary),
+ sourceSets.toSet()
+ ),
+ dci = DCI(setOf(dri), JavadocContentKind.OverviewSummary),
+ sourceSets = sourceSets.toSet(),
+ style = emptySet(),
+ extra = PropertyContainer.empty()
+ )
+ }
+ )
+ }
+}
+
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPlugin.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPlugin.kt
new file mode 100644
index 00000000..0c322a82
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/JavadocPlugin.kt
@@ -0,0 +1,53 @@
+package org.jetbrains.dokka.javadoc
+
+import org.jetbrains.dokka.javadoc.JavadocDocumentableToPageTranslator
+import org.jetbrains.dokka.javadoc.location.JavadocLocationProviderFactory
+import org.jetbrains.dokka.javadoc.renderer.KorteJavadocRenderer
+import org.jetbrains.dokka.javadoc.signatures.JavadocSignatureProvider
+import org.jetbrains.dokka.CoreExtensions
+import org.jetbrains.dokka.base.DokkaBase
+import org.jetbrains.dokka.kotlinAsJava.KotlinAsJavaPlugin
+import org.jetbrains.dokka.plugability.DokkaPlugin
+import org.jetbrains.dokka.plugability.querySingle
+
+class JavadocPlugin : DokkaPlugin() {
+
+ val dokkaBasePlugin by lazy { plugin<DokkaBase>() }
+ val kotinAsJavaPlugin by lazy { plugin<KotlinAsJavaPlugin>() }
+
+ val locationProviderFactory by extensionPoint<JavadocLocationProviderFactory>()
+
+ val dokkaJavadocPlugin by extending {
+ (CoreExtensions.renderer
+ providing { ctx -> KorteJavadocRenderer(dokkaBasePlugin.querySingle { outputWriter }, ctx, "views") }
+ override dokkaBasePlugin.htmlRenderer)
+ }
+
+ val pageTranslator by extending {
+ CoreExtensions.documentableToPageTranslator providing { context ->
+ JavadocDocumentableToPageTranslator(
+ dokkaBasePlugin.querySingle { commentsToContentConverter },
+ dokkaBasePlugin.querySingle { signatureProvider },
+ context.logger
+ )
+ } override dokkaBasePlugin.documentableToPageTranslator
+ }
+
+ val javadocLocationProviderFactory by extending {
+ locationProviderFactory providing { context ->
+ JavadocLocationProviderFactory(context)
+ }
+ }
+
+ val javadocSignatureProvider by extending {
+ val dokkaBasePlugin = plugin<DokkaBase>()
+ dokkaBasePlugin.signatureProvider providing { ctx ->
+ JavadocSignatureProvider(
+ ctx.single(
+ dokkaBasePlugin.commentsToContentConverter
+ ), ctx.logger
+ )
+ } override kotinAsJavaPlugin.javaSignatureProvider
+ }
+}
+
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProvider.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProvider.kt
new file mode 100644
index 00000000..7f27ff18
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProvider.kt
@@ -0,0 +1,129 @@
+package org.jetbrains.dokka.javadoc.location
+
+import org.jetbrains.dokka.javadoc.pages.*
+import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
+import org.jetbrains.dokka.base.resolvers.local.BaseLocationProvider
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.links.Nullable
+import org.jetbrains.dokka.links.parent
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.pages.ContentPage
+import org.jetbrains.dokka.pages.PageNode
+import org.jetbrains.dokka.pages.RootPageNode
+import org.jetbrains.dokka.plugability.DokkaContext
+import java.util.*
+
+class JavadocLocationProvider(pageRoot: RootPageNode, dokkaContext: DokkaContext) :
+ BaseLocationProvider(dokkaContext) {
+
+ private val pathIndex = IdentityHashMap<PageNode, List<String>>().apply {
+ fun registerPath(page: PageNode, prefix: List<String> = emptyList()) {
+ val packagePath = page.takeIf { it is JavadocPackagePageNode }?.name.orEmpty()
+ .replace(".", "/")
+ val newPathPrefix = prefix + packagePath
+
+ val path = (prefix + when (page) {
+ is AllClassesPage -> listOf("allclasses")
+ is TreeViewPage -> if (page.classes == null)
+ listOf("overview-tree")
+ else
+ listOf("package-tree")
+ is ContentPage -> if (page.dri.isNotEmpty() && page.dri.first().classNames != null)
+ listOfNotNull(page.dri.first().classNames)
+ else if (page is JavadocPackagePageNode)
+ listOf(packagePath, "package-summary")
+ else
+ listOf("index")
+ else -> emptyList()
+ }).filterNot { it.isEmpty() }
+
+ put(page, path)
+ page.children.forEach { registerPath(it, newPathPrefix) }
+
+ }
+ put(pageRoot, listOf("index"))
+ pageRoot.children.forEach { registerPath(it) }
+ }
+
+ private val nodeIndex = HashMap<DRI, PageNode>().apply {
+ fun registerNode(node: PageNode) {
+ if (node is ContentPage) put(node.dri.first(), node)
+ node.children.forEach(::registerNode)
+ }
+ registerNode(pageRoot)
+ }
+
+ private operator fun IdentityHashMap<PageNode, List<String>>.get(dri: DRI) = this[nodeIndex[dri]]
+
+ private fun List<String>.relativeTo(context: List<String>): String {
+ val contextPath = context.dropLast(1)
+ val commonPathElements = zip(contextPath).takeWhile { (a, b) -> a == b }.count()
+ return (List(contextPath.size - commonPathElements) { ".." } + this.drop(commonPathElements)).joinToString("/")
+ }
+
+ private fun JavadocClasslikePageNode.findAnchorableByDRI(dri: DRI): AnchorableJavadocNode? =
+ (constructors + methods + entries + properties).firstOrNull { it.dri == dri }
+
+ override fun resolve(dri: DRI, sourceSets: Set<DokkaSourceSet>, context: PageNode?): String {
+ return nodeIndex[dri]?.let { resolve(it, context) }
+ ?: nodeIndex[dri.parent]?.let {
+ val anchor = when (val anchorElement = (it as? JavadocClasslikePageNode)?.findAnchorableByDRI(dri)) {
+ is JavadocFunctionNode -> anchorElement.getAnchor()
+ is JavadocEntryNode -> anchorElement.name
+ is JavadocPropertyNode -> anchorElement.name
+ else -> anchorForDri(dri)
+ }
+ "${resolve(it, context, skipExtension = true)}.html#$anchor"
+ }
+ ?: getExternalLocation(dri, sourceSets)
+ }
+
+ private fun JavadocFunctionNode.getAnchor(): String =
+ "$name(${parameters.joinToString(",") {
+ when (val bound = if (it.typeBound is org.jetbrains.dokka.model.Nullable) it.typeBound.inner else it.typeBound) {
+ is TypeConstructor -> bound.dri.classNames.orEmpty()
+ is OtherParameter -> bound.name
+ is PrimitiveJavaType -> bound.name
+ is UnresolvedBound -> bound.name
+ is JavaObject -> "Object"
+ else -> bound.toString()
+ }
+ }})"
+
+ fun anchorForFunctionNode(node: JavadocFunctionNode) = node.getAnchor()
+
+ private fun anchorForDri(dri: DRI): String =
+ dri.callable?.let { callable ->
+ "${callable.name}(${callable.params.joinToString(",") {
+ ((it as? Nullable)?.wrapped ?: it).toString()
+ }})"
+ } ?: dri.classNames.orEmpty()
+
+ override fun resolve(node: PageNode, context: PageNode?, skipExtension: Boolean): String =
+ pathIndex[node]?.relativeTo(pathIndex[context].orEmpty())?.let {
+ if (skipExtension) it.removeSuffix(".html") else it
+ } ?: run {
+ throw IllegalStateException("Path for ${node::class.java.canonicalName}:${node.name} not found")
+ }
+
+ fun resolve(link: LinkJavadocListEntry, contextRoot: PageNode? = null, skipExtension: Boolean = true) =
+ pathIndex[link.dri.first()]?.let {
+ when (link.kind) {
+ JavadocContentKind.Class -> it
+ JavadocContentKind.OverviewSummary -> it.dropLast(1) + "index"
+ JavadocContentKind.PackageSummary -> it.dropLast(1) + "package-summary"
+ JavadocContentKind.AllClasses -> it.dropLast(1) + "allclasses"
+ JavadocContentKind.OverviewTree -> it.dropLast(1) + "overview-tree"
+ JavadocContentKind.PackageTree -> it.dropLast(1) + "package-tree"
+ else -> it
+ }
+ }?.relativeTo(pathIndex[contextRoot].orEmpty())?.let { if (skipExtension) "$it.html" else it }.orEmpty()
+
+ override fun resolveRoot(node: PageNode): String {
+ TODO("Not yet implemented")
+ }
+
+ override fun ancestors(node: PageNode): List<PageNode> {
+ TODO("Not yet implemented")
+ }
+}
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProviderFactory.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProviderFactory.kt
new file mode 100644
index 00000000..7dd8abe1
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/location/JavadocLocationProviderFactory.kt
@@ -0,0 +1,11 @@
+package org.jetbrains.dokka.javadoc.location
+
+import org.jetbrains.dokka.base.resolvers.local.LocationProviderFactory
+import org.jetbrains.dokka.pages.RootPageNode
+import org.jetbrains.dokka.plugability.DokkaContext
+
+class JavadocLocationProviderFactory(private val context: DokkaContext) : LocationProviderFactory {
+
+ override fun getLocationProvider(pageNode: RootPageNode) =
+ JavadocLocationProvider(pageNode, context)
+} \ No newline at end of file
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocContentNodes.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocContentNodes.kt
new file mode 100644
index 00000000..d45837b7
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocContentNodes.kt
@@ -0,0 +1,173 @@
+package org.jetbrains.dokka.javadoc.pages
+
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.DokkaConfiguration.DokkaSourceSet
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.pages.*
+
+enum class JavadocContentKind : Kind {
+ AllClasses, OverviewSummary, PackageSummary, Class, OverviewTree, PackageTree
+}
+
+abstract class JavadocContentNode(
+ dri: Set<DRI>,
+ kind: Kind,
+ override val sourceSets: Set<DokkaSourceSet>
+) : ContentNode {
+ override val dci: DCI = DCI(dri, kind)
+ override val style: Set<Style> = emptySet()
+ override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
+ override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentNode = this
+}
+
+interface JavadocList {
+ val tabTitle: String
+ val colTitle: String
+ val children: List<JavadocListEntry>
+}
+
+interface JavadocListEntry {
+ val stringTag: String
+}
+
+class EmptyNode(
+ dri: DRI,
+ kind: Kind,
+ override val sourceSets: Set<DokkaSourceSet>,
+ override val extra: PropertyContainer<ContentNode> = PropertyContainer.empty()
+) : ContentNode {
+ override val dci: DCI = DCI(setOf(dri), kind)
+ override val style: Set<Style> = emptySet()
+
+ override fun withNewExtras(newExtras: PropertyContainer<ContentNode>): ContentNode =
+ EmptyNode(dci.dri.first(), dci.kind, sourceSets, newExtras)
+
+ override fun hasAnyContent(): Boolean = false
+}
+
+class JavadocContentGroup(
+ val dri: Set<DRI>,
+ val kind: Kind,
+ sourceSets: Set<DokkaSourceSet>,
+ override val children: List<JavadocContentNode>
+) : JavadocContentNode(dri, kind, sourceSets) {
+
+ companion object {
+ operator fun invoke(
+ dri: Set<DRI>,
+ kind: Kind,
+ sourceSets: Set<DokkaSourceSet>,
+ block: JavaContentGroupBuilder.() -> Unit
+ ): JavadocContentGroup =
+ JavadocContentGroup(dri, kind, sourceSets, JavaContentGroupBuilder(sourceSets).apply(block).list)
+ }
+
+ override fun hasAnyContent(): Boolean = children.isNotEmpty()
+}
+
+class JavaContentGroupBuilder(val sourceSets: Set<DokkaSourceSet>) {
+ val list = mutableListOf<JavadocContentNode>()
+}
+
+class TitleNode(
+ val title: String,
+ val subtitle: List<ContentNode>,
+ val version: String,
+ val parent: String?,
+ val dri: Set<DRI>,
+ val kind: Kind,
+ sourceSets: Set<DokkaSourceSet>
+) : JavadocContentNode(dri, kind, sourceSets) {
+ override fun hasAnyContent(): Boolean = !title.isBlank() || !version.isBlank() || subtitle.isNotEmpty()
+}
+
+fun JavaContentGroupBuilder.title(
+ title: String,
+ subtitle: List<ContentNode>,
+ version: String,
+ parent: String? = null,
+ dri: Set<DRI>,
+ kind: Kind
+) {
+ list.add(TitleNode(title, subtitle, version, parent, dri, kind, sourceSets))
+}
+
+class RootListNode(
+ val entries: List<LeafListNode>,
+ val dri: Set<DRI>,
+ val kind: Kind,
+ sourceSets: Set<DokkaSourceSet>,
+) : JavadocContentNode(dri, kind, sourceSets) {
+ override fun hasAnyContent(): Boolean = children.isNotEmpty()
+}
+
+class LeafListNode(
+ val tabTitle: String,
+ val colTitle: String,
+ val entries: List<JavadocListEntry>,
+ val dri: Set<DRI>,
+ val kind: Kind,
+ sourceSets: Set<DokkaSourceSet>
+) : JavadocContentNode(dri, kind, sourceSets) {
+ override fun hasAnyContent(): Boolean = children.isNotEmpty()
+}
+
+
+fun JavaContentGroupBuilder.rootList(
+ dri: Set<DRI>,
+ kind: Kind,
+ rootList: List<JavadocList>
+) {
+ val children = rootList.map {
+ LeafListNode(it.tabTitle, it.colTitle, it.children, dri, kind, sourceSets)
+ }
+ list.add(RootListNode(children, dri, kind, sourceSets))
+}
+
+fun JavaContentGroupBuilder.leafList(
+ dri: Set<DRI>,
+ kind: Kind,
+ leafList: JavadocList
+) {
+ list.add(LeafListNode(leafList.tabTitle, leafList.colTitle, leafList.children, dri, kind, sourceSets))
+}
+
+fun JavadocList(tabTitle: String, colTitle: String, children: List<JavadocListEntry>) = object : JavadocList {
+ override val tabTitle = tabTitle
+ override val colTitle = colTitle
+ override val children = children
+}
+
+class LinkJavadocListEntry(
+ val name: String,
+ val dri: Set<DRI>,
+ val kind: Kind = ContentKind.Symbol,
+ val sourceSets: Set<DokkaSourceSet>
+) :
+ JavadocListEntry {
+ override val stringTag: String
+ get() = if (builtString == null)
+ throw IllegalStateException("stringTag for LinkJavadocListEntry accessed before build() call")
+ else builtString!!
+
+ private var builtString: String? = null
+
+ fun build(body: (String, Set<DRI>, Kind, List<DokkaSourceSet>) -> String) {
+ builtString = body(name, dri, kind, sourceSets.toList())
+ }
+}
+
+data class RowJavadocListEntry(val link: LinkJavadocListEntry, val doc: List<ContentNode>) : JavadocListEntry {
+ override val stringTag: String = ""
+}
+
+data class JavadocSignatureContentNode(
+ val dri: DRI,
+ val kind: Kind = ContentKind.Symbol,
+ val annotations: ContentNode?,
+ val modifiers: ContentNode?,
+ val signatureWithoutModifiers: ContentNode,
+ val supertypes: ContentNode?
+): JavadocContentNode(setOf(dri), kind, signatureWithoutModifiers.sourceSets) {
+ override fun hasAnyContent(): Boolean = true
+}
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocIndexExtra.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocIndexExtra.kt
new file mode 100644
index 00000000..08a90613
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocIndexExtra.kt
@@ -0,0 +1,10 @@
+package org.jetbrains.dokka.javadoc.pages
+
+import org.jetbrains.dokka.model.Documentable
+import org.jetbrains.dokka.model.properties.ExtraProperty
+import org.jetbrains.dokka.pages.ContentNode
+
+data class JavadocIndexExtra(val index: List<ContentNode>) : ExtraProperty<Documentable> {
+ override val key: ExtraProperty.Key<Documentable, *> = JavadocIndexExtra
+ companion object : ExtraProperty.Key<Documentable, JavadocIndexExtra>
+} \ No newline at end of file
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocPageNodes.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocPageNodes.kt
new file mode 100644
index 00000000..790e15c5
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/JavadocPageNodes.kt
@@ -0,0 +1,436 @@
+package org.jetbrains.dokka.javadoc.pages
+
+import com.intellij.psi.PsiClass
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.analysis.DescriptorDocumentableSource
+import org.jetbrains.dokka.analysis.PsiDocumentableSource
+import org.jetbrains.dokka.analysis.from
+import org.jetbrains.dokka.base.renderers.sourceSets
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.model.properties.WithExtraProperties
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.kotlin.descriptors.ClassDescriptor
+import org.jetbrains.kotlin.descriptors.ClassKind
+import org.jetbrains.kotlin.resolve.DescriptorUtils.getClassDescriptorForType
+import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
+
+interface JavadocPageNode : ContentPage
+
+interface WithJavadocExtra<T : Documentable> : WithExtraProperties<T> {
+ override fun withNewExtras(newExtras: PropertyContainer<T>): T =
+ throw IllegalStateException("Merging extras is not applicable for javadoc")
+}
+
+class JavadocModulePageNode(
+ override val name: String,
+ override val content: JavadocContentNode,
+ override val children: List<PageNode>,
+ override val dri: Set<DRI>
+) :
+ RootPageNode(),
+ JavadocPageNode {
+
+ override val documentable: Documentable? = null
+ override val embeddedResources: List<String> = emptyList()
+ override fun modified(name: String, children: List<PageNode>): RootPageNode =
+ JavadocModulePageNode(name, content, children, dri)
+
+ override fun modified(
+ name: String,
+ content: ContentNode,
+ dri: Set<DRI>,
+ embeddedResources: List<String>,
+ children: List<PageNode>
+ ): ContentPage = JavadocModulePageNode(name, content as JavadocContentNode, children, dri)
+}
+
+class JavadocPackagePageNode(
+ override val name: String,
+ override val content: JavadocContentNode,
+ override val dri: Set<DRI>,
+
+ override val documentable: Documentable? = null,
+ override val children: List<PageNode> = emptyList(),
+ override val embeddedResources: List<String> = listOf()
+) : JavadocPageNode {
+
+ override fun modified(
+ name: String,
+ children: List<PageNode>
+ ): PageNode = JavadocPackagePageNode(
+ name,
+ content,
+ dri,
+ documentable,
+ children,
+ embeddedResources
+ )
+
+ override fun modified(
+ name: String,
+ content: ContentNode,
+ dri: Set<DRI>,
+ embeddedResources: List<String>,
+ children: List<PageNode>
+ ): ContentPage =
+ JavadocPackagePageNode(
+ name,
+ content as JavadocContentNode,
+ dri,
+ documentable,
+ children,
+ embeddedResources
+ )
+}
+
+sealed class AnchorableJavadocNode(open val dri: DRI)
+
+data class JavadocEntryNode(
+ override val dri: DRI,
+ val name: String,
+ val signature: JavadocSignatureContentNode,
+ val brief: List<ContentNode>,
+ override val extra: PropertyContainer<DEnumEntry> = PropertyContainer.empty()
+): AnchorableJavadocNode(dri), WithJavadocExtra<DEnumEntry>
+
+data class JavadocParameterNode(
+ override val dri: DRI,
+ val name: String,
+ val type: ContentNode,
+ val description: List<ContentNode>,
+ val typeBound: Bound,
+ override val extra: PropertyContainer<DParameter> = PropertyContainer.empty()
+): AnchorableJavadocNode(dri), WithJavadocExtra<DParameter>
+
+data class JavadocPropertyNode(
+ override val dri: DRI,
+ val name: String,
+ val signature: JavadocSignatureContentNode,
+ val brief: List<ContentNode>,
+ override val extra: PropertyContainer<DProperty> = PropertyContainer.empty()
+): AnchorableJavadocNode(dri), WithJavadocExtra<DProperty>
+
+data class JavadocFunctionNode(
+ val signature: JavadocSignatureContentNode,
+ val brief: List<ContentNode>,
+ val parameters: List<JavadocParameterNode>,
+ val name: String,
+ override val dri: DRI,
+ override val extra: PropertyContainer<DFunction> = PropertyContainer.empty()
+): AnchorableJavadocNode(dri), WithJavadocExtra<DFunction> {
+ val isInherited: Boolean
+ get() {
+ val extra = extra[InheritedFunction]
+ return extra?.inheritedFrom?.keys?.firstOrNull { it.analysisPlatform == Platform.jvm }?.let { jvm ->
+ extra.isInherited(jvm)
+ } ?: false
+ }
+}
+
+class JavadocClasslikePageNode(
+ override val name: String,
+ override val content: JavadocContentNode,
+ override val dri: Set<DRI>,
+ val signature: JavadocSignatureContentNode,
+ val description: List<ContentNode>,
+ val constructors: List<JavadocFunctionNode>,
+ val methods: List<JavadocFunctionNode>,
+ val entries: List<JavadocEntryNode>,
+ val classlikes: List<JavadocClasslikePageNode>,
+ val properties: List<JavadocPropertyNode>,
+ override val documentable: Documentable? = null,
+ override val children: List<PageNode> = emptyList(),
+ override val embeddedResources: List<String> = listOf(),
+ override val extra: PropertyContainer<DClasslike> = PropertyContainer.empty(),
+) : JavadocPageNode, WithJavadocExtra<DClasslike> {
+
+ val kind: String? = documentable?.kind()
+ val packageName = dri.first().packageName
+
+ override fun modified(
+ name: String,
+ children: List<PageNode>
+ ): PageNode = JavadocClasslikePageNode(
+ name,
+ content,
+ dri,
+ signature,
+ description,
+ constructors,
+ methods,
+ entries,
+ classlikes,
+ properties,
+ documentable,
+ children,
+ embeddedResources,
+ extra
+ )
+
+ override fun modified(
+ name: String,
+ content: ContentNode,
+ dri: Set<DRI>,
+ embeddedResources: List<String>,
+ children: List<PageNode>
+ ): ContentPage =
+ JavadocClasslikePageNode(
+ name,
+ content as JavadocContentNode,
+ dri,
+ signature,
+ description,
+ constructors,
+ methods,
+ entries,
+ classlikes,
+ properties,
+ documentable,
+ children,
+ embeddedResources,
+ extra
+ )
+}
+
+class AllClassesPage(val classes: List<JavadocClasslikePageNode>) : JavadocPageNode {
+ val classEntries =
+ classes.map { LinkJavadocListEntry(it.name, it.dri, ContentKind.Classlikes, it.sourceSets().toSet()) }
+
+ override val name: String = "All Classes"
+ override val dri: Set<DRI> = setOf(DRI.topLevel)
+
+ override val documentable: Documentable? = null
+ override val embeddedResources: List<String> = emptyList()
+
+ override val content: ContentNode =
+ EmptyNode(
+ DRI.topLevel,
+ ContentKind.Classlikes,
+ classes.flatMap { it.sourceSets() }.toSet()
+ )
+
+ override fun modified(
+ name: String,
+ content: ContentNode,
+ dri: Set<DRI>,
+ embeddedResources: List<String>,
+ children: List<PageNode>
+ ): ContentPage = TODO()
+
+ override fun modified(name: String, children: List<PageNode>): PageNode =
+ TODO()
+
+ override val children: List<PageNode> = emptyList()
+
+}
+
+class TreeViewPage(
+ override val name: String,
+ val packages: List<JavadocPackagePageNode>?,
+ val classes: List<JavadocClasslikePageNode>?,
+ override val dri: Set<DRI>,
+ override val documentable: Documentable?,
+ val root: PageNode
+) : JavadocPageNode {
+ init {
+ assert(packages == null || classes == null)
+ assert(packages != null || classes != null)
+ }
+
+ private val documentables = root.children.filterIsInstance<ContentPage>().flatMap { node ->
+ getDocumentableEntries(node)
+ }.groupBy({ it.first }) { it.second }.map { (l, r) -> l to r.first() }.toMap()
+
+ private val descriptorMap = getDescriptorMap()
+ private val inheritanceTuple = generateInheritanceTree()
+ internal val classGraph = inheritanceTuple.first
+ internal val interfaceGraph = inheritanceTuple.second
+
+ override val children: List<PageNode> = emptyList()
+
+ val title = when (documentable) {
+ is DPackage -> "$name Class Hierarchy"
+ else -> "All packages"
+ }
+
+ val kind = when (documentable) {
+ is DPackage -> "package"
+ else -> "main"
+ }
+
+ override fun modified(
+ name: String,
+ content: ContentNode,
+ dri: Set<DRI>,
+ embeddedResources: List<String>,
+ children: List<PageNode>
+ ): ContentPage =
+ TreeViewPage(
+ name,
+ packages = children.filterIsInstance<JavadocPackagePageNode>().takeIf { it.isNotEmpty() },
+ classes = children.filterIsInstance<JavadocClasslikePageNode>().takeIf { it.isNotEmpty() },
+ dri = dri,
+ documentable = documentable,
+ root = root
+ )
+
+ override fun modified(name: String, children: List<PageNode>): PageNode =
+ TreeViewPage(
+ name,
+ packages = children.filterIsInstance<JavadocPackagePageNode>().takeIf { it.isNotEmpty() },
+ classes = children.filterIsInstance<JavadocClasslikePageNode>().takeIf { it.isNotEmpty() },
+ dri = dri,
+ documentable = documentable,
+ root = root
+ )
+
+ override val embeddedResources: List<String> = emptyList()
+
+ override val content: ContentNode = EmptyNode(
+ DRI.topLevel,
+ ContentKind.Classlikes,
+ emptySet()
+ )
+
+ private fun generateInheritanceTree(): Pair<List<InheritanceNode>, List<InheritanceNode>> {
+ val mergeMap = mutableMapOf<DRI, InheritanceNode>()
+
+ fun addToMap(info: InheritanceNode, map: MutableMap<DRI, InheritanceNode>) {
+ if (map.containsKey(info.dri))
+ map.computeIfPresent(info.dri) { _, info2 ->
+ info.copy(children = (info.children + info2.children).distinct())
+ }!!.children.forEach { addToMap(it, map) }
+ else
+ map[info.dri] = info
+ }
+
+ fun collect(dri: DRI): InheritanceNode =
+ InheritanceNode(
+ dri,
+ mergeMap[dri]?.children.orEmpty().map { collect(it.dri) },
+ mergeMap[dri]?.interfaces.orEmpty(),
+ mergeMap[dri]?.isInterface ?: false
+ )
+
+ fun classTreeRec(node: InheritanceNode): List<InheritanceNode> = if (node.isInterface) {
+ node.children.flatMap(::classTreeRec)
+ } else {
+ listOf(node.copy(children = node.children.flatMap(::classTreeRec)))
+ }
+
+ fun classTree(node: InheritanceNode) = classTreeRec(node).singleOrNull()
+
+ fun interfaceTreeRec(node: InheritanceNode): List<InheritanceNode> = if (node.isInterface) {
+ listOf(node.copy(children = node.children.filter { it.isInterface }))
+ } else {
+ node.children.flatMap(::interfaceTreeRec)
+ }
+
+ fun interfaceTree(node: InheritanceNode) = interfaceTreeRec(node).firstOrNull() // TODO.single()
+
+ fun gatherPsiClasses(psi: PsiClass): List<Pair<PsiClass, List<PsiClass>>> = psi.supers.toList().let { l ->
+ listOf(psi to l) + l.flatMap { gatherPsiClasses(it) }
+ }
+
+ val psiInheritanceTree = documentables.flatMap { (_, v) -> (v as? WithExpectActual)?.sources?.values.orEmpty() }
+ .filterIsInstance<PsiDocumentableSource>().mapNotNull { it.psi as? PsiClass }.flatMap(::gatherPsiClasses)
+ .flatMap { entry -> entry.second.map { it to entry.first } }
+ .let {
+ it + it.map { it.second to null }
+ }
+ .groupBy({ it.first }) { it.second }
+ .map { it.key to it.value.filterNotNull().distinct() }
+ .map { (k, v) ->
+ InheritanceNode(
+ DRI.from(k),
+ v.map { InheritanceNode(DRI.from(it)) },
+ k.supers.filter { it.isInterface }.map { DRI.from(it) },
+ k.isInterface
+ )
+
+ }
+
+ val descriptorInheritanceTree = descriptorMap.flatMap { (_, v) ->
+ v.typeConstructor.supertypes
+ .map { getClassDescriptorForType(it) to v }
+ }
+ .let {
+ it + it.map { it.second to null }
+ }
+ .groupBy({ it.first }) { it.second }
+ .map { it.key to it.value.filterNotNull().distinct() }
+ .map { (k, v) ->
+ InheritanceNode(
+ DRI.from(k),
+ v.map { InheritanceNode(DRI.from(it)) },
+ k.typeConstructor.supertypes.map { getClassDescriptorForType(it) }
+ .mapNotNull { cd -> cd.takeIf { it.kind == ClassKind.INTERFACE }?.let { DRI.from(it) } },
+ isInterface = k.kind == ClassKind.INTERFACE
+ )
+ }
+
+ descriptorInheritanceTree.forEach { addToMap(it, mergeMap) }
+ psiInheritanceTree.forEach { addToMap(it, mergeMap) }
+
+ val rootNodes = mergeMap.entries.filter {
+ it.key.classNames in setOf("Any", "Object") //TODO: Probably should be matched by DRI, not just className
+ }.map {
+ collect(it.value.dri)
+ }
+
+ return rootNodes.let { Pair(it.mapNotNull(::classTree), it.mapNotNull(::interfaceTree)) }
+ }
+
+ private fun generateInterfaceGraph() {
+ documentables.values.filterIsInstance<DInterface>()
+ }
+
+ private fun getDocumentableEntries(node: ContentPage): List<Pair<DRI, Documentable>> =
+ listOfNotNull(node.documentable?.let { it.dri to it }) +
+ node.children.filterIsInstance<ContentPage>().flatMap(::getDocumentableEntries)
+
+ private fun getDescriptorMap(): Map<DRI, ClassDescriptor> {
+ val map: MutableMap<DRI, ClassDescriptor> = mutableMapOf()
+ documentables
+ .mapNotNull { (k, v) ->
+ v.descriptorForPlatform()?.let { k to it }?.also { (k, v) -> map[k] = v }
+ }.map { it.second }.forEach { gatherSupertypes(it, map) }
+
+ return map.toMap()
+ }
+
+ private fun gatherSupertypes(descriptor: ClassDescriptor, map: MutableMap<DRI, ClassDescriptor>) {
+ map.putIfAbsent(DRI.from(descriptor), descriptor)
+ descriptor.typeConstructor.supertypes.map { getClassDescriptorForType(it) }
+ .forEach { gatherSupertypes(it, map) }
+ }
+
+ private fun Documentable?.descriptorForPlatform(platform: Platform = Platform.jvm) =
+ (this as? WithExpectActual).descriptorForPlatform(platform)
+
+ private fun WithExpectActual?.descriptorForPlatform(platform: Platform = Platform.jvm) = this?.let {
+ it.sources.entries.find { it.key.analysisPlatform == platform }?.value?.let { it as? DescriptorDocumentableSource }?.descriptor as? ClassDescriptor
+ }
+
+ data class InheritanceNode(
+ val dri: DRI,
+ val children: List<InheritanceNode> = emptyList(),
+ val interfaces: List<DRI> = emptyList(),
+ val isInterface: Boolean = false
+ ) {
+ override fun equals(other: Any?): Boolean = other is InheritanceNode && other.dri == dri
+ override fun hashCode(): Int = dri.hashCode()
+ }
+}
+
+private fun Documentable.kind(): String? =
+ when (this) {
+ is DClass -> "class"
+ is DEnum -> "enum"
+ is DAnnotation -> "annotation"
+ is DObject -> "object"
+ is DInterface -> "interface"
+ else -> null
+ } \ No newline at end of file
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/htmlPreprocessors.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/htmlPreprocessors.kt
new file mode 100644
index 00000000..42276dd4
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/htmlPreprocessors.kt
@@ -0,0 +1,68 @@
+package org.jetbrains.dokka.javadoc.pages
+
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.transformers.pages.PageTransformer
+
+val preprocessors = listOf(ResourcesInstaller, TreeViewInstaller, AllClassesPageInstaller)
+
+object ResourcesInstaller : PageTransformer {
+ override fun invoke(input: RootPageNode): RootPageNode = input.modified(
+ children = input.children +
+ RendererSpecificResourcePage(
+ "resourcePack",
+ emptyList(),
+ RenderingStrategy.Copy("/static_res")
+ )
+ )
+}
+
+object TreeViewInstaller : PageTransformer {
+ override fun invoke(input: RootPageNode): RootPageNode = install(input, input) as RootPageNode
+
+ private fun install(node: PageNode, root: RootPageNode): PageNode = when (node) {
+ is JavadocModulePageNode -> installOverviewTreeNode(node, root)
+ is JavadocPackagePageNode -> installPackageTreeNode(node, root)
+ else -> node
+ }
+
+ private fun installOverviewTreeNode(node: JavadocModulePageNode, root: RootPageNode): JavadocModulePageNode {
+ val overviewTree = TreeViewPage(
+ name = "Class Hierarchy",
+ packages = node.children<JavadocPackagePageNode>().map { installPackageTreeNode(it, root) },
+ classes = null,
+ dri = node.dri,
+ documentable = node.documentable,
+ root = root
+ )
+
+ return node.modified(children = node.children.map { node ->
+ install(
+ node,
+ root
+ )
+ } + overviewTree) as JavadocModulePageNode
+ }
+
+ private fun installPackageTreeNode(node: JavadocPackagePageNode, root: RootPageNode): JavadocPackagePageNode {
+ val packageTree = TreeViewPage(
+ name = "${node.name}",
+ packages = null,
+ classes = node.children.filterIsInstance<JavadocClasslikePageNode>(),
+ dri = node.dri,
+ documentable = node.documentable,
+ root = root
+ )
+
+ return node.modified(children = node.children + packageTree) as JavadocPackagePageNode
+ }
+}
+
+object AllClassesPageInstaller : PageTransformer {
+ override fun invoke(input: RootPageNode): RootPageNode {
+ val classes = (input as JavadocModulePageNode).children.filterIsInstance<JavadocPackagePageNode>().flatMap {
+ it.children.filterIsInstance<JavadocClasslikePageNode>()
+ }
+
+ return input.modified(children = input.children + AllClassesPage(classes))
+ }
+} \ No newline at end of file
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/pages.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/pages.kt
new file mode 100644
index 00000000..14aa1903
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/pages/pages.kt
@@ -0,0 +1,17 @@
+package org.jetbrains.dokka.javadoc.pages
+
+internal const val jQueryVersion = "3.3.1"
+internal const val jQueryMigrateVersion = "3.0.1"
+
+//class PackageSummary(val page: PageNode) : RendererSpecificPage {
+// override val name = "package-summary"
+// override val children = emptyList<PageNode>()
+// override fun modified(name: String, children: List<PageNode>) = this
+//
+// override val strategy = RenderingStrategy.Write(content())
+//
+// private fun content(): String = pageStart(page.name, "0.0.1", page.name, "../") + // TODO
+// topNavbar(page, "???")
+//
+//}
+
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToHtmlTranslator.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToHtmlTranslator.kt
new file mode 100644
index 00000000..906c9d5a
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToHtmlTranslator.kt
@@ -0,0 +1,63 @@
+package org.jetbrains.dokka.javadoc.renderer
+
+import org.jetbrains.dokka.javadoc.location.JavadocLocationProvider
+import org.jetbrains.dokka.javadoc.pages.JavadocSignatureContentNode
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.utilities.formatToEndWithHtml
+import org.jetbrains.dokka.utilities.htmlEscape
+
+internal class JavadocContentToHtmlTranslator(
+ private val locationProvider: JavadocLocationProvider,
+ private val context: DokkaContext
+) {
+
+ fun htmlForContentNode(node: ContentNode, relative: PageNode?): String =
+ when (node) {
+ is ContentGroup -> htmlForContentNodes(node.children, node.style, relative)
+ is ContentText -> buildText(node)
+ is ContentDRILink -> buildLink(
+ locationProvider.resolve(node.address, node.sourceSets, relative),
+ htmlForContentNodes(node.children, node.style, relative)
+ )
+ is ContentResolvedLink -> buildLink(node.address, htmlForContentNodes(node.children, node.style, relative))
+ is ContentCode -> htmlForCode(node.children)
+ is JavadocSignatureContentNode -> htmlForSignature(node, relative)
+ else -> ""
+ }
+
+ fun htmlForContentNodes(list: List<ContentNode>, styles: Set<Style>, relative: PageNode?) =
+ list.joinToString(separator = "") { htmlForContentNode(it, relative) }
+
+ private fun buildText(node: ContentText): String {
+ val escapedText = node.text.htmlEscape()
+ return if (node.style.contains(ContentStyle.InDocumentationAnchor)) {
+ """<em><a id="$escapedText" class="searchTagResult">${escapedText}</a></em>"""
+ } else {
+ escapedText
+ }
+ }
+
+ private fun htmlForCode(code: List<ContentNode>): String = code.map { element ->
+ when (element) {
+ is ContentText -> element.text
+ is ContentBreakLine -> ""
+ else -> run { context.logger.error("Cannot cast $element as ContentText!"); "" }
+ }
+ }.joinToString("<br>", """<span class="code">""", "</span>") { it }
+
+ private fun htmlForSignature(node: JavadocSignatureContentNode, relative: PageNode?): String =
+ listOfNotNull(
+ node.annotations,
+ node.modifiers,
+ node.signatureWithoutModifiers,
+ node.supertypes
+ ).joinToString(separator = " ") { htmlForContentNode(it, relative) }
+
+ companion object {
+
+ fun buildLink(address: String, content: String) =
+ """<a href=${address.formatToEndWithHtml()}>$content</a>"""
+
+ }
+} \ No newline at end of file
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToTemplateMapTranslator.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToTemplateMapTranslator.kt
new file mode 100644
index 00000000..1a286095
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/JavadocContentToTemplateMapTranslator.kt
@@ -0,0 +1,222 @@
+package org.jetbrains.dokka.javadoc.renderer
+
+import org.jetbrains.dokka.javadoc.location.JavadocLocationProvider
+import org.jetbrains.dokka.javadoc.pages.*
+import org.jetbrains.dokka.javadoc.toNormalized
+import org.jetbrains.dokka.Platform
+import org.jetbrains.dokka.base.resolvers.local.LocationProvider
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.links.sureClassNames
+import org.jetbrains.dokka.model.ImplementedInterfaces
+import org.jetbrains.dokka.model.InheritedFunction
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.plugability.DokkaContext
+import java.nio.file.Paths
+
+internal class JavadocContentToTemplateMapTranslator(
+ private val locationProvider: JavadocLocationProvider,
+ private val context: DokkaContext,
+) {
+
+ fun templateMapForPageNode(node: JavadocPageNode): TemplateMap =
+ mapOf<String, Any?>(
+ "docName" to "docName", // todo docname
+ "pathToRoot" to pathToRoot(node),
+ "contextRoot" to node,
+ "kind" to "main",
+ ) + templateMapForNode(node)
+
+
+ private fun templateMapForNode(node: JavadocPageNode): TemplateMap =
+ when (node) {
+ is JavadocModulePageNode -> InnerTranslator(node).templateMapForJavadocContentNode(node.content)
+ is JavadocClasslikePageNode -> InnerTranslator(node).templateMapForClasslikeNode(node)
+ is JavadocPackagePageNode -> InnerTranslator(node).templateMapForPackagePageNode(node)
+ is TreeViewPage -> InnerTranslator(node).templateMapForTreeViewPage(node)
+ is AllClassesPage -> InnerTranslator(node).templateMapForAllClassesPage(node)
+ else -> emptyMap()
+ }
+
+ private fun pathToRoot(node: JavadocPageNode): String {
+ return when(node){
+ is JavadocModulePageNode -> ""
+ else -> run {
+ val link = locationProvider.resolve(node, skipExtension = true)
+ val dir = Paths.get(link).parent?.toNormalized().orEmpty()
+ return dir.split("/").filter { it.isNotEmpty() }.joinToString("/") { ".." }.let {
+ if (it.isNotEmpty()) "$it/" else it
+ }
+ }
+ }
+ }
+
+ private inner class InnerTranslator(val contextNode: PageNode) {
+
+ private val htmlTranslator = JavadocContentToHtmlTranslator(locationProvider, context)
+
+ fun templateMapForAllClassesPage(node: AllClassesPage): TemplateMap {
+ return mapOf(
+ "title" to "All Classes",
+ "list" to node.classEntries
+ )
+ }
+
+ fun templateMapForTreeViewPage(node: TreeViewPage): TemplateMap {
+ return mapOf(
+ "title" to node.title,
+ "name" to node.name,
+ "kind" to node.kind,
+ "list" to node.packages.orEmpty() + node.classes.orEmpty(),
+ "classGraph" to node.classGraph,
+ "interfaceGraph" to node.interfaceGraph
+ )
+ }
+
+ fun templateMapForPackagePageNode(node: JavadocPackagePageNode): TemplateMap {
+ return mapOf(
+ "kind" to "package"
+ ) + templateMapForJavadocContentNode(node.content)
+ }
+
+ fun templateMapForFunctionNode(node: JavadocFunctionNode): TemplateMap {
+ return mapOf(
+ "brief" to htmlForContentNodes(node.brief, contextNode),
+ "parameters" to node.parameters.map { templateMapForParameterNode(it) },
+ "inlineParameters" to node.parameters.joinToString { renderInlineParameter(it) },
+ "anchorLink" to locationProvider.anchorForFunctionNode(node),
+ "signature" to templateMapForSignatureNode(node.signature),
+ "name" to node.name
+ )
+ }
+
+ fun templateMapForClasslikeNode(node: JavadocClasslikePageNode): TemplateMap =
+ mapOf(
+ "constructors" to node.constructors.map { templateMapForFunctionNode(it) },
+ "signature" to templateMapForSignatureNode(node.signature),
+ "methods" to templateMapForClasslikeMethods(node.methods),
+ "classlikeDocumentation" to htmlForContentNodes(node.description, node),
+ "entries" to node.entries.map { templateMapForEntryNode(it) },
+ "properties" to node.properties.map { templateMapForPropertyNode(it) },
+ "classlikes" to node.classlikes.map { templateMapForNestedClasslikeNode(it) },
+ "implementedInterfaces" to templateMapForImplementedInterfaces(node).sorted(),
+ "kind" to node.kind,
+ "packageName" to node.packageName,
+ "name" to node.name
+ ) + templateMapForJavadocContentNode(node.content)
+
+ fun templateMapForSignatureNode(node: JavadocSignatureContentNode): TemplateMap =
+ mapOf(
+ "annotations" to node.annotations?.let { htmlForContentNode(it, contextNode) },
+ "signatureWithoutModifiers" to htmlForContentNode(node.signatureWithoutModifiers, contextNode),
+ "modifiers" to node.modifiers?.let { htmlForContentNode(it, contextNode) },
+ "supertypes" to node.supertypes?.let { htmlForContentNode(it, contextNode) }
+ )
+
+ fun templateMapForJavadocContentNode(node: JavadocContentNode): TemplateMap =
+ when (node) {
+ is TitleNode -> templateMapForTitleNode(node)
+ is JavadocContentGroup -> templateMapForJavadocContentGroup(node)
+ is LeafListNode -> templateMapForLeafListNode(node)
+ is RootListNode -> templateMapForRootListNode(node)
+ else -> emptyMap()
+ }
+
+ private fun templateMapForParameterNode(node: JavadocParameterNode): TemplateMap =
+ mapOf(
+ "description" to htmlForContentNodes(node.description, contextNode),
+ "name" to node.name,
+ "type" to htmlForContentNode(node.type, contextNode)
+ )
+
+ private fun templateMapForImplementedInterfaces(node: JavadocClasslikePageNode) =
+ node.extra[ImplementedInterfaces]?.interfaces?.entries?.firstOrNull { it.key.analysisPlatform == Platform.jvm }?.value?.map { it.displayable() } // TODO: REMOVE HARDCODED JVM DEPENDENCY
+ .orEmpty()
+
+ private fun templateMapForClasslikeMethods(nodes: List<JavadocFunctionNode>): TemplateMap {
+ val (inherited, own) = nodes.partition { it.isInherited }
+ return mapOf(
+ "own" to own.map { templateMapForFunctionNode(it) },
+ "inherited" to inherited.map { templateMapForInheritedMethod(it) }
+ .groupBy { it["inheritedFrom"] as String }.entries.map {
+ mapOf(
+ "inheritedFrom" to it.key,
+ "names" to it.value.map { it["name"] as String }.sorted().joinToString()
+ )
+ }
+ )
+ }
+
+ private fun templateMapForInheritedMethod(node: JavadocFunctionNode): TemplateMap {
+ val inheritedFrom = node.extra[InheritedFunction]?.inheritedFrom
+ return mapOf(
+ "inheritedFrom" to inheritedFrom?.entries?.firstOrNull { it.key.analysisPlatform == Platform.jvm }?.value?.displayable() // TODO: REMOVE HARDCODED JVM DEPENDENCY
+ .orEmpty(),
+ "name" to node.name
+ )
+ }
+
+ private fun templateMapForNestedClasslikeNode(node: JavadocClasslikePageNode): TemplateMap {
+ return mapOf(
+ "modifiers" to node.signature.modifiers?.let { htmlForContentNode(it, contextNode) },
+ "signature" to node.name,
+ "description" to htmlForContentNodes(node.description, node)
+ )
+ }
+
+ private fun templateMapForPropertyNode(node: JavadocPropertyNode): TemplateMap {
+ return mapOf(
+ "modifiers" to node.signature.modifiers?.let { htmlForContentNode(it, contextNode) },
+ "signature" to htmlForContentNode(node.signature.signatureWithoutModifiers, contextNode),
+ "description" to htmlForContentNodes(node.brief, contextNode)
+ )
+ }
+
+ private fun templateMapForEntryNode(node: JavadocEntryNode): TemplateMap {
+ return mapOf(
+ "signature" to templateMapForSignatureNode(node.signature),
+ "brief" to htmlForContentNodes(node.brief, contextNode)
+ )
+ }
+
+ private fun templateMapForTitleNode(node: TitleNode): TemplateMap {
+ return mapOf(
+ "title" to node.title,
+ "subtitle" to htmlForContentNodes(node.subtitle, contextNode),
+ "version" to node.version,
+ "packageName" to node.parent
+ )
+ }
+
+ private fun templateMapForJavadocContentGroup(note: JavadocContentGroup): TemplateMap {
+ return note.children.fold(emptyMap()) { map, child ->
+ map + templateMapForJavadocContentNode(child)
+ }
+ }
+
+ private fun templateMapForLeafListNode(node: LeafListNode): TemplateMap {
+ return mapOf(
+ "tabTitle" to node.tabTitle,
+ "colTitle" to node.colTitle,
+ "list" to node.entries
+ )
+ }
+
+ private fun templateMapForRootListNode(node: RootListNode): TemplateMap {
+ return mapOf(
+ "lists" to node.entries.map { templateMapForLeafListNode(it) }
+ )
+ }
+
+ private fun renderInlineParameter(parameter: JavadocParameterNode): String =
+ htmlForContentNode(parameter.type, contextNode) + " ${parameter.name}"
+
+ private fun htmlForContentNode(node: ContentNode, relativeNode: PageNode) =
+ htmlTranslator.htmlForContentNode(node, relativeNode)
+
+ private fun htmlForContentNodes(nodes: List<ContentNode>, relativeNode: PageNode) =
+ htmlTranslator.htmlForContentNodes(nodes, emptySet(), relativeNode)
+ }
+
+ private fun DRI.displayable(): String = "${packageName}.${sureClassNames}"
+}
+
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/KorteJavadocRenderer.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/KorteJavadocRenderer.kt
new file mode 100644
index 00000000..092fed2c
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/KorteJavadocRenderer.kt
@@ -0,0 +1,190 @@
+package org.jetbrains.dokka.javadoc.renderer
+
+import com.soywiz.korte.*
+import org.jetbrains.dokka.javadoc.location.JavadocLocationProvider
+import org.jetbrains.dokka.javadoc.pages.*
+import org.jetbrains.dokka.javadoc.renderer.JavadocContentToHtmlTranslator.Companion.buildLink
+import org.jetbrains.dokka.javadoc.toNormalized
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.jetbrains.dokka.base.renderers.OutputWriter
+import org.jetbrains.dokka.javadoc.JavadocPlugin
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.plugability.DokkaContext
+import org.jetbrains.dokka.plugability.plugin
+import org.jetbrains.dokka.plugability.querySingle
+import org.jetbrains.dokka.renderers.Renderer
+import org.jetbrains.kotlin.utils.addToStdlib.safeAs
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.time.LocalDate
+
+typealias TemplateMap = Map<String, Any?>
+
+class KorteJavadocRenderer(private val outputWriter: OutputWriter, val context: DokkaContext, resourceDir: String) :
+ Renderer {
+ private lateinit var locationProvider: JavadocLocationProvider
+
+ private val contentToHtmlTranslator by lazy {
+ JavadocContentToHtmlTranslator(locationProvider, context)
+ }
+
+ private val contentToTemplateMapTranslator by lazy {
+ JavadocContentToTemplateMapTranslator(locationProvider, context)
+ }
+
+ override fun render(root: RootPageNode) = root.let { preprocessors.fold(root) { r, t -> t.invoke(r) } }.let { newRoot ->
+ locationProvider = context.plugin<JavadocPlugin>().querySingle { locationProviderFactory }.getLocationProvider(newRoot)
+ runBlocking(Dispatchers.IO) {
+ renderModulePageNode(newRoot as JavadocModulePageNode)
+ SearchScriptsCreator(locationProvider).invoke(newRoot).forEach { renderSpecificPage(it, "") }
+ }
+ }
+
+ private fun templateForNode(node: JavadocPageNode) = when (node) {
+ is JavadocModulePageNode,
+ is JavadocPackagePageNode -> "tabPage.korte"
+ is JavadocClasslikePageNode -> "class.korte"
+ is AllClassesPage -> "listPage.korte"
+ is TreeViewPage -> "treePage.korte"
+ else -> ""
+ }
+
+ private fun CoroutineScope.renderNode(node: PageNode, path: String = "") {
+ if (node is JavadocPageNode) {
+ renderJavadocPageNode(node)
+ } else if (node is RendererSpecificPage) {
+ renderSpecificPage(node, path)
+ }
+ }
+
+ private fun CoroutineScope.renderModulePageNode(node: JavadocModulePageNode) {
+ val link = "."
+ val name = "index"
+
+ val contentMap = contentToTemplateMapTranslator.templateMapForPageNode(node)
+
+ writeFromTemplate(outputWriter, "$link/$name".toNormalized(), "tabPage.korte", contentMap.toList())
+ node.children.forEach { renderNode(it, link) }
+ }
+
+ private fun CoroutineScope.renderJavadocPageNode(node: JavadocPageNode) {
+ val link = locationProvider.resolve(node, skipExtension = true)
+ val contentMap = contentToTemplateMapTranslator.templateMapForPageNode(node)
+ writeFromTemplate(outputWriter, link, templateForNode(node), contentMap.toList())
+ node.children.forEach { renderNode(it, link.toNormalized()) }
+ }
+
+ private fun CoroutineScope.renderSpecificPage(node: RendererSpecificPage, path: String) = launch {
+ when (val strategy = node.strategy) {
+ is RenderingStrategy.Copy -> outputWriter.writeResources(strategy.from, "")
+ is RenderingStrategy.Write -> outputWriter.writeHtml(node.name, strategy.text)
+ is RenderingStrategy.Callback -> outputWriter.writeResources(
+ path,
+ strategy.instructions(this@KorteJavadocRenderer, node)
+ )
+ RenderingStrategy.DoNothing -> Unit
+ }
+ }
+
+ private fun Pair<String, String>.pairToTag() =
+ """<th class="colFirst" scope="row">${first}</th><td class="colLast">${second}</td>"""
+
+ private fun DRI.toLink(context: PageNode? = null) = locationProvider.resolve(this, emptySet(), context)
+
+ private suspend fun OutputWriter.writeHtml(path: String, text: String) = write(path, text, "")
+ private fun CoroutineScope.writeFromTemplate(
+ writer: OutputWriter,
+ path: String,
+ template: String,
+ args: List<Pair<String, *>>
+ ) = launch {
+ val tmp = templateRenderer.render(template, *(args.toTypedArray()))
+ writer.writeHtml("$path.html", tmp)
+ }
+
+ private fun getTemplateConfig() = TemplateConfig().also { config ->
+ listOf(
+ TeFunction("curDate") { LocalDate.now() },
+ TeFunction("jQueryVersion") { "3.1" },
+ TeFunction("jQueryMigrateVersion") { "1.2.1" },
+ TeFunction("rowColor") { args -> if ((args.first() as Int) % 2 == 0) "altColor" else "rowColor" },
+ TeFunction("h1Title") { args -> if ((args.first() as? String) == "package") "title=\"Package\" " else "" },
+ TeFunction("createTabRow") { args ->
+ val (link, doc) = args.first() as RowJavadocListEntry
+ val contextRoot = args[1] as PageNode?
+ (buildLink(
+ locationProvider.resolve(link, contextRoot),
+ link.name
+ ) to contentToHtmlTranslator.htmlForContentNodes(doc, emptySet(), contextRoot)).pairToTag().trim()
+ },
+ TeFunction("createListRow") { args ->
+ val link = args.first() as LinkJavadocListEntry
+ val contextRoot = args[1] as PageNode?
+ buildLink(
+ locationProvider.resolve(link, contextRoot),
+ link.name
+ )
+ },
+ TeFunction("createPackageHierarchy") { args ->
+ val list = args.first() as List<JavadocPackagePageNode>
+ list.mapIndexed { i, p ->
+ val content = if (i + 1 == list.size) "" else ", "
+ val name = p.name
+ "<li><a href=\"$name/package-tree.html\">$name</a>$content</li>"
+ }.joinToString("\n")
+ },
+ TeFunction("renderInheritanceGraph") { args ->
+ val rootNodes = args.first() as List<TreeViewPage.InheritanceNode>
+
+ fun drawRec(node: TreeViewPage.InheritanceNode): String =
+ "<li class=\"circle\">" + node.dri.let { dri ->
+ listOfNotNull(
+ dri.packageName,
+ dri.classNames
+ ).joinToString(".") + node.interfaces.takeUnless { node.isInterface || it.isEmpty() }
+ ?.let {
+ " implements " + it.joinToString(", ") { n ->
+ listOfNotNull(
+ n.packageName,
+ buildLink(n.toLink(), n.classNames.orEmpty())
+ ).joinToString(".")
+ }
+ }.orEmpty()
+ } + node.children.filterNot { it.isInterface }.takeUnless { it.isEmpty() }?.let {
+ "<ul>" + it.joinToString("\n", transform = ::drawRec) + "</ul>"
+ }.orEmpty() + "</li>"
+
+ rootNodes.joinToString { drawRec(it) }
+ },
+ Filter("length") { subject.dynamicLength() },
+ TeFunction("hasAnyDescription") { args ->
+ args.first().safeAs<List<HashMap<String, String>>>()
+ ?.any { it["description"]?.trim()?.isNotEmpty() ?: false }
+ }
+ ).forEach {
+ when (it) {
+ is TeFunction -> config.register(it)
+ is Filter -> config.register(it)
+ is Tag -> config.register(it)
+ }
+ }
+ }
+
+ private val config = getTemplateConfig()
+ private val templateRenderer = Templates(
+ ResourceTemplateProvider(
+ resourceDir
+ ), config = config, cache = true
+ )
+
+ private class ResourceTemplateProvider(val basePath: String) : TemplateProvider {
+ override suspend fun get(template: String): String =
+ javaClass.classLoader.getResourceAsStream("$basePath/$template")?.bufferedReader()?.lines()?.toArray()
+ ?.joinToString("\n") ?: throw IllegalStateException("Template not found: $basePath/$template")
+ }
+
+}
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/SearchScriptsCreator.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/SearchScriptsCreator.kt
new file mode 100644
index 00000000..696c2e80
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/renderer/SearchScriptsCreator.kt
@@ -0,0 +1,261 @@
+package org.jetbrains.dokka.javadoc.renderer
+
+import org.jetbrains.dokka.javadoc.location.JavadocLocationProvider
+import org.jetbrains.dokka.javadoc.pages.*
+import org.jetbrains.dokka.javadoc.renderer.SearchRecord.Companion.allTypes
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.base.renderers.sourceSets
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.Documentable
+import org.jetbrains.dokka.model.InheritedFunction
+import org.jetbrains.dokka.model.doc.Index
+import org.jetbrains.dokka.model.properties.WithExtraProperties
+import org.jetbrains.dokka.pages.*
+import org.jetbrains.dokka.utilities.formatToEndWithHtml
+import org.jetbrains.dokka.utilities.htmlEscape
+import java.lang.StringBuilder
+
+class SearchScriptsCreator(private val locationProvider: JavadocLocationProvider) {
+
+ fun invoke(input: RootPageNode): List<RendererSpecificPage> {
+ val data = when (input) {
+ is JavadocModulePageNode -> processModules(listOf(input))
+ else -> SearchData()
+ }
+ val serializer = SearchRecordJsonSerializer()
+
+ val modules = RendererSpecificResourcePage(
+ name = "module-search-index.js",
+ children = emptyList(),
+ strategy = RenderingStrategy.Write(serializer.serialize(data.moduleRecords, "moduleSearchIndex"))
+ )
+
+ val packages = RendererSpecificResourcePage(
+ name = "package-search-index.js",
+ children = emptyList(),
+ strategy = RenderingStrategy.Write(serializer.serialize(data.packageRecords, "packageSearchIndex"))
+ )
+
+ val types = RendererSpecificResourcePage(
+ name = "type-search-index.js",
+ children = emptyList(),
+ strategy = RenderingStrategy.Write(serializer.serialize(data.typeRecords, "typeSearchIndex"))
+ )
+
+ val members = RendererSpecificResourcePage(
+ name = "member-search-index.js",
+ children = emptyList(),
+ strategy = RenderingStrategy.Write(serializer.serialize(data.memberRecords, "memberSearchIndex"))
+ )
+
+ val indexes = RendererSpecificResourcePage(
+ name = "tag-search-index.js",
+ children = emptyList(),
+ strategy = RenderingStrategy.Write(serializer.serialize(data.searchIndexes, "tagSearchIndex"))
+ )
+
+ return listOf(modules, packages, types, members, indexes)
+ }
+
+ private fun processModules(input: List<JavadocModulePageNode>): SearchData {
+ val modules = SearchData(moduleRecords = input.map { SearchRecord(l = it.name, url = locationProvider.resolve(it).formatToEndWithHtml()) })
+ val processablePackages = input.flatMap { it.children.filterIsInstance<JavadocPackagePageNode>() }
+ return processPackages(processablePackages, modules)
+ }
+
+ private fun processPackages(input: List<JavadocPackagePageNode>, accumulator: SearchData): SearchData {
+ val packages = input.map { SearchRecord(l = it.name, url = locationProvider.resolve(it).formatToEndWithHtml()) } + SearchRecord.allPackages
+ val types = input.flatMap {
+ it.children.filterIsInstance<JavadocClasslikePageNode>().map { classlike -> it to classlike }
+ }
+ val updated = accumulator.copy(packageRecords = packages)
+ return processTypes(types, updated)
+ }
+
+ private fun processTypes(
+ input: List<Pair<JavadocPackagePageNode, JavadocClasslikePageNode>>,
+ accumulator: SearchData
+ ): SearchData {
+ val types = input.map {
+ SearchRecord(
+ p = it.first.name,
+ l = it.second.name,
+ url = locationProvider.resolve(it.second).formatToEndWithHtml()
+ )
+ } + allTypes
+ val updated = accumulator.copy(typeRecords = types)
+ return processMembers(input, updated).copy(searchIndexes = indexSearchForClasslike(input))
+ }
+
+ private fun processMembers(
+ input: List<Pair<JavadocPackagePageNode, JavadocClasslikePageNode>>,
+ accumulator: SearchData
+ ): SearchData {
+ val functions = input.flatMap {
+ (it.second.constructors + it.second.methods).withoutInherited().map { function ->
+ SearchRecordCreator.function(
+ packageName = it.first.name,
+ classlikeName = it.second.name,
+ input = function,
+ url = locationProvider.resolve(function.dri, it.first.sourceSets())
+ )
+ }
+ }
+
+ val properties = input.flatMap {
+ it.second.properties.map { property ->
+ SearchRecordCreator.property(
+ packageName = it.first.name,
+ classlikeName = it.second.name,
+ property,
+ locationProvider.resolve(property.dri, it.first.sourceSets())
+ )
+ }
+ }
+
+ val entries = input.flatMap {
+ it.second.entries.map { entry ->
+ SearchRecordCreator.entry(
+ packageName = it.first.name,
+ classlikeName = it.second.name,
+ entry,
+ locationProvider.resolve(entry.dri, it.first.sourceSets())
+ )
+ }
+ }
+
+ return accumulator.copy(memberRecords = functions + properties + entries)
+ }
+
+ private fun indexSearchForClasslike(
+ input: List<Pair<JavadocPackagePageNode, JavadocClasslikePageNode>>,
+ ): List<SearchRecord> {
+ val indexesForClasslike = input.flatMap {
+ val indexes = it.second.indexes()
+ indexes.map { index ->
+ val label = renderNode(index)
+ SearchRecord(
+ p = it.first.name,
+ c = it.second.name,
+ l = label,
+ url = resolveUrlForSearchIndex(it.second.dri.first(), it.second.sourceSets(), label)
+ )
+ }
+ }
+
+ val indexesForMemberNodes = input.flatMap { packageWithClasslike ->
+ (packageWithClasslike.second.constructors +
+ packageWithClasslike.second.methods.withoutInherited() +
+ packageWithClasslike.second.properties +
+ packageWithClasslike.second.entries
+ ).map { it to it.indexes() }
+ .flatMap { entryWithIndex ->
+ entryWithIndex.second.map {
+ val label = renderNode(it)
+ SearchRecord(
+ p = packageWithClasslike.first.name,
+ c = packageWithClasslike.second.name,
+ l = label,
+ url = resolveUrlForSearchIndex(
+ entryWithIndex.first.dri,
+ packageWithClasslike.second.sourceSets(),
+ label
+ )
+ )
+ }
+ }
+ }
+
+ return indexesForClasslike + indexesForMemberNodes
+ }
+
+ private fun <T : Documentable> WithJavadocExtra<T>.indexes(): List<ContentNode> = extra[JavadocIndexExtra]?.index.orEmpty()
+
+ private fun List<JavadocFunctionNode>.withoutInherited(): List<JavadocFunctionNode> = filter { !it.isInherited }
+
+ private fun resolveUrlForSearchIndex(dri: DRI, sourceSets: Set<DokkaConfiguration.DokkaSourceSet>, label: String): String =
+ locationProvider.resolve(dri, sourceSets).formatToEndWithHtml() + "#" + label
+}
+
+private data class SearchRecord(
+ val p: String? = null,
+ val c: String? = null,
+ val l: String,
+ val url: String? = null
+) {
+ companion object {
+ val allPackages = SearchRecord(l = "All packages", url = "index.html")
+ val allTypes = SearchRecord(l = "All classes", url = "allclasses.html")
+ }
+}
+
+private object SearchRecordCreator {
+ fun function(
+ packageName: String,
+ classlikeName: String,
+ input: JavadocFunctionNode,
+ url: String
+ ): SearchRecord =
+ SearchRecord(
+ p = packageName,
+ c = classlikeName,
+ l = input.name + input.parameters.joinToString(
+ prefix = "(",
+ postfix = ")"
+ ) { renderNode(it.type) },
+ url = url.formatToEndWithHtml()
+ )
+
+ fun property(
+ packageName: String,
+ classlikeName: String,
+ input: JavadocPropertyNode,
+ url: String
+ ): SearchRecord =
+ SearchRecord(
+ p = packageName,
+ c = classlikeName,
+ l = input.name,
+ url = url.formatToEndWithHtml()
+ )
+
+ fun entry(packageName: String, classlikeName: String, input: JavadocEntryNode, url: String): SearchRecord =
+ SearchRecord(
+ p = packageName,
+ c = classlikeName,
+ l = input.name,
+ url = url.formatToEndWithHtml()
+ )
+}
+
+private data class SearchData(
+ val moduleRecords: List<SearchRecord> = emptyList(),
+ val packageRecords: List<SearchRecord> = emptyList(),
+ val typeRecords: List<SearchRecord> = emptyList(),
+ val memberRecords: List<SearchRecord> = emptyList(),
+ val searchIndexes: List<SearchRecord> = emptyList()
+)
+
+private class SearchRecordJsonSerializer {
+ fun serialize(record: SearchRecord): String {
+ val serialized = StringBuilder()
+ serialized.append("{")
+ with(record) {
+ if (p != null) serialized.append("\"p\":\"$p\",")
+ if (c != null) serialized.append("\"c\":\"$c\",")
+ serialized.append("\"l\":\"$l\"")
+ if (url != null) serialized.append(",\"url\":\"$url\"")
+ }
+ serialized.append("}")
+ return serialized.toString()
+ }
+
+ fun serialize(records: List<SearchRecord>, variable: String): String =
+ "var " + variable + " = " + records.joinToString(prefix = "[", postfix = "]") { serialize(it) }
+}
+
+private fun renderNode(node: ContentNode): String =
+ when (node) {
+ is ContentText -> node.text
+ else -> node.children.joinToString(separator = "") { renderNode(it) }
+ } \ No newline at end of file
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/signatures/JavadocSignatureProvider.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/signatures/JavadocSignatureProvider.kt
new file mode 100644
index 00000000..a60d7711
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/signatures/JavadocSignatureProvider.kt
@@ -0,0 +1,201 @@
+package org.jetbrains.dokka.javadoc.signatures
+
+import org.jetbrains.dokka.javadoc.translators.documentables.JavadocPageContentBuilder
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.base.signatures.JvmSignatureUtils
+import org.jetbrains.dokka.base.signatures.SignatureProvider
+import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter
+import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder
+import org.jetbrains.dokka.kotlinAsJava.signatures.JavaSignatureUtils
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.links.sureClassNames
+import org.jetbrains.dokka.model.*
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.pages.ContentKind
+import org.jetbrains.dokka.pages.ContentNode
+import org.jetbrains.dokka.utilities.DokkaLogger
+
+class JavadocSignatureProvider(ctcc: CommentsToContentConverter, logger: DokkaLogger) : SignatureProvider,
+ JvmSignatureUtils by JavaSignatureUtils {
+
+ private val contentBuilder = JavadocPageContentBuilder(ctcc, this, logger)
+
+ private val ignoredVisibilities = setOf(JavaVisibility.Default)
+
+ private val ignoredModifiers =
+ setOf(KotlinModifier.Open, JavaModifier.Empty, KotlinModifier.Empty, KotlinModifier.Sealed)
+
+ override fun signature(documentable: Documentable): List<ContentNode> = when (documentable) {
+ is DFunction -> signature(documentable)
+ is DProperty -> signature(documentable)
+ is DClasslike -> signature(documentable)
+ is DEnumEntry -> signature(documentable)
+ is DTypeParameter -> signature(documentable)
+ is DParameter -> signature(documentable)
+ else -> throw NotImplementedError(
+ "Cannot generate signature for ${documentable::class.qualifiedName} ${documentable.name}"
+ )
+ }
+
+ private fun signature(c: DClasslike): List<ContentNode> =
+ javadocSignature(c) {
+ annotations {
+ annotationsBlock(c)
+ }
+ modifiers {
+ text(c.visibility[it]?.takeIf { it !in ignoredVisibilities }?.name?.plus(" ") ?: "")
+
+ if (c is DClass) {
+ text(c.modifier[it]?.takeIf { it !in ignoredModifiers }?.name?.plus(" ") ?: "")
+ text(c.modifiers()[it]?.toSignatureString() ?: "")
+ }
+
+ when (c) {
+ is DClass -> text("class")
+ is DInterface -> text("interface")
+ is DEnum -> text("enum")
+ is DObject -> text("class")
+ is DAnnotation -> text("@interface")
+ }
+ }
+ signatureWithoutModifiers {
+ link(c.name!!, c.dri)
+ if (c is WithGenerics) {
+ list(c.generics, prefix = "<", suffix = ">") {
+ +buildSignature(it)
+ }
+ }
+ }
+ supertypes {
+ if (c is WithSupertypes) {
+ c.supertypes.map { (p, dris) ->
+ val (classes, interfaces) = dris.partition { it.kind == JavaClassKindTypes.CLASS }
+ list(classes, prefix = "extends ", sourceSets = setOf(p)) {
+ link(it.dri.sureClassNames, it.dri, sourceSets = setOf(p))
+ }
+ list(interfaces, prefix = " implements ", sourceSets = setOf(p)){
+ link(it.dri.sureClassNames, it.dri, sourceSets = setOf(p))
+ }
+ }
+ }
+ }
+ }
+
+ private fun signature(f: DFunction): List<ContentNode> =
+ javadocSignature(f) {
+ annotations {
+ annotationsBlock(f)
+ }
+ modifiers {
+ text(f.modifier[it]?.takeIf { it !in ignoredModifiers }?.name?.plus(" ") ?: "")
+ text(f.modifiers()[it]?.toSignatureString() ?: "")
+ list(f.generics, prefix = "<", suffix = "> ") {
+ +buildSignature(it)
+ }
+ signatureForProjection(f.type)
+ }
+ signatureWithoutModifiers {
+ link(f.name, f.dri)
+ text("(")
+ list(f.parameters) {
+ annotationsInline(it)
+ text(it.modifiers()[it]?.toSignatureString().orEmpty())
+ signatureForProjection(it.type)
+ text(Typography.nbsp.toString())
+ text(it.name.orEmpty())
+ }
+ text(")")
+ }
+ }
+
+ private fun signature(p: DProperty): List<ContentNode> =
+ javadocSignature(p) {
+ annotations {
+ annotationsBlock(p)
+ }
+ modifiers {
+ text(p.visibility[it]?.takeIf { it !in ignoredVisibilities }?.name?.plus(" ") ?: "")
+ text(p.modifier[it]?.name + " ")
+ text(p.modifiers()[it]?.toSignatureString() ?: "")
+ signatureForProjection(p.type)
+ }
+ signatureWithoutModifiers {
+ link(p.name, p.dri)
+ }
+ }
+
+ private fun signature(e: DEnumEntry): List<ContentNode> =
+ javadocSignature(e) {
+ annotations {
+ annotationsBlock(e)
+ }
+ modifiers {
+ text(e.modifiers()[it]?.toSignatureString() ?: "")
+ }
+ signatureWithoutModifiers {
+ link(e.name, e.dri)
+ }
+ }
+
+ private fun signature(t: DTypeParameter): List<ContentNode> =
+ javadocSignature(t) {
+ annotations {
+ annotationsBlock(t)
+ }
+ signatureWithoutModifiers {
+ text(t.name)
+ }
+ supertypes {
+ list(t.bounds, prefix = "extends ") {
+ signatureForProjection(it)
+ }
+ }
+ }
+
+ private fun signature(p: DParameter): List<ContentNode> =
+ javadocSignature(p) {
+ modifiers {
+ signatureForProjection(p.type)
+ }
+ signatureWithoutModifiers {
+ link(p.name.orEmpty(), p.dri)
+ }
+ }
+
+ private fun javadocSignature(
+ d: Documentable,
+ extra: PropertyContainer<ContentNode> = PropertyContainer.empty(),
+ block: JavadocPageContentBuilder.JavadocContentBuilder.(DokkaConfiguration.DokkaSourceSet) -> Unit
+ ): List<ContentNode> =
+ d.sourceSets.map { sourceSet ->
+ contentBuilder.contentFor(d, ContentKind.Main) {
+ with(contentBuilder) {
+ javadocGroup(d.dri, d.sourceSets, extra) {
+ block(sourceSet)
+ }
+ }
+ }
+ }
+
+ private fun PageContentBuilder.DocumentableContentBuilder.signatureForProjection(p: Projection): Unit = when (p) {
+ is OtherParameter -> link(p.name, p.declarationDRI)
+ is TypeConstructor -> group {
+ link(p.dri.classNames.orEmpty(), p.dri)
+ list(p.projections, prefix = "<", suffix = ">") {
+ signatureForProjection(it)
+ }
+ }
+ is Variance -> group {
+ text(p.kind.toString() + " ")
+ signatureForProjection(p.inner)
+ }
+ is Star -> text("?")
+ is Nullable -> signatureForProjection(p.inner)
+ is JavaObject, is Dynamic -> link("Object", DRI("java.lang", "Object"))
+ is Void -> text("void")
+ is PrimitiveJavaType -> text(p.name)
+ is UnresolvedBound -> text(p.name)
+ }
+
+ private fun DRI.fqName(): String = "${packageName.orEmpty()}.${classNames.orEmpty()}"
+}
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/translators/documentables/JavadocPageContentBuilder.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/translators/documentables/JavadocPageContentBuilder.kt
new file mode 100644
index 00000000..e70f8370
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/translators/documentables/JavadocPageContentBuilder.kt
@@ -0,0 +1,80 @@
+package org.jetbrains.dokka.javadoc.translators.documentables
+
+import org.jetbrains.dokka.javadoc.pages.JavadocSignatureContentNode
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.base.signatures.SignatureProvider
+import org.jetbrains.dokka.base.transformers.pages.comments.CommentsToContentConverter
+import org.jetbrains.dokka.base.translators.documentables.PageContentBuilder
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.model.properties.PropertyContainer
+import org.jetbrains.dokka.pages.ContentKind
+import org.jetbrains.dokka.pages.ContentNode
+import org.jetbrains.dokka.utilities.DokkaLogger
+import java.lang.IllegalStateException
+
+class JavadocPageContentBuilder(
+ commentsConverter: CommentsToContentConverter,
+ signatureProvider: SignatureProvider,
+ logger: DokkaLogger
+) : PageContentBuilder(commentsConverter, signatureProvider, logger) {
+
+ fun PageContentBuilder.DocumentableContentBuilder.javadocGroup(
+ dri: DRI = mainDRI.first(),
+ sourceSets: Set<DokkaConfiguration.DokkaSourceSet> = mainSourcesetData,
+ extra: PropertyContainer<ContentNode> = mainExtra,
+ block: JavadocContentBuilder.() -> Unit
+ ) {
+ +JavadocContentBuilder(
+ mainDri = dri,
+ mainExtra = extra,
+ mainSourceSet = sourceSets,
+ ).apply(block).build()
+ }
+
+ open inner class JavadocContentBuilder(
+ private val mainDri: DRI,
+ private val mainExtra: PropertyContainer<ContentNode>,
+ private val mainSourceSet: Set<DokkaConfiguration.DokkaSourceSet>,
+ ) {
+ var annotations: ContentNode? = null
+ var modifiers: ContentNode? = null
+ var signatureWithoutModifiers: ContentNode? = null
+ var supertypes: ContentNode? = null
+
+ fun annotations(block: PageContentBuilder.DocumentableContentBuilder.() -> Unit) {
+ val built = buildContentForBlock(block)
+ if(built.hasAnyContent()) annotations = built
+ }
+
+ fun modifiers(block: PageContentBuilder.DocumentableContentBuilder.() -> Unit) {
+ val built = buildContentForBlock(block)
+ if(built.hasAnyContent()) modifiers = built
+ }
+
+ fun signatureWithoutModifiers(block: PageContentBuilder.DocumentableContentBuilder.() -> Unit) {
+ signatureWithoutModifiers = buildContentForBlock(block)
+ }
+
+ fun supertypes(block: PageContentBuilder.DocumentableContentBuilder.() -> Unit) {
+ val built = buildContentForBlock(block)
+ if(built.hasAnyContent()) supertypes = built
+ }
+
+ private fun buildContentForBlock(block: PageContentBuilder.DocumentableContentBuilder.() -> Unit) =
+ contentFor(
+ dri = mainDri,
+ sourceSets = mainSourceSet,
+ kind = ContentKind.Symbol,
+ extra = mainExtra,
+ block = block
+ )
+
+ fun build(): JavadocSignatureContentNode = JavadocSignatureContentNode(
+ dri = mainDri,
+ annotations = annotations,
+ modifiers = modifiers,
+ signatureWithoutModifiers = signatureWithoutModifiers ?: throw IllegalStateException("JavadocSignatureContentNode should have at least a signature"),
+ supertypes = supertypes
+ )
+ }
+} \ No newline at end of file
diff --git a/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/utils.kt b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/utils.kt
new file mode 100644
index 00000000..97c35b28
--- /dev/null
+++ b/plugins/javadoc/src/main/kotlin/org/jetbrains/dokka/javadoc/utils.kt
@@ -0,0 +1,8 @@
+package org.jetbrains.dokka.javadoc
+
+import java.nio.file.Path
+import java.nio.file.Paths
+
+internal fun Path.toNormalized() = this.normalize().toFile().toString()
+
+internal fun String.toNormalized() = Paths.get(this).toNormalized()