All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.dokka.javadoc.location.JavadocLocationProvider.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 */

package org.jetbrains.dokka.javadoc.location

import org.jetbrains.dokka.base.resolvers.local.DefaultLocationProvider
import org.jetbrains.dokka.javadoc.pages.*
import org.jetbrains.dokka.links.DRI
import org.jetbrains.dokka.links.Nullable
import org.jetbrains.dokka.links.PointingToDeclaration
import org.jetbrains.dokka.model.DisplaySourceSet
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.*

public class JavadocLocationProvider(
    pageRoot: RootPageNode,
    dokkaContext: DokkaContext
) : DefaultLocationProvider(pageRoot, dokkaContext) {

    private val pathIndex = IdentityHashMap>().apply {
        fun registerPath(page: PageNode, prefix: List = 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 if (page is IndexPage)
                    listOf("index-files", page.name)
                else if (page is DeprecatedPage)
                    listOf("deprecated")
                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 parentPageIndex = HashMap()
    private val nodeIndex = HashMap().apply {
        fun registerNode(node: PageNode) {
            if (node is ContentPage) put(node.dri.first(), node)
            (node as? JavadocClasslikePageNode)?.getAnchorables()?.forEach { navigableNode ->
                parentPageIndex[navigableNode.getDRI()] = node
            }
            node.children.forEach(::registerNode)
        }
        registerNode(pageRoot)
    }

    private operator fun IdentityHashMap>.get(dri: DRI) = this[nodeIndex[dri]]

    private fun List.relativeTo(context: List): String {
        val contextPath = context.dropLast(1).flatMap { it.split("/") }
        val commonPathElements = flatMap { it.split("/") }.zip(contextPath).takeWhile { (a, b) -> a == b }.count()
        return (List(contextPath.size - commonPathElements) { ".." } + this.flatMap { it.split("/") }.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, context: PageNode?): String? =
        getLocalLocation(dri, context)
            ?: getLocalLocation(dri.copy(target = PointingToDeclaration), context)
            // Not found in PageGraph, that means it's an external link
            ?: getExternalLocation(dri, sourceSets)
            ?: getExternalLocation(dri.copy(target = PointingToDeclaration), sourceSets)

    private fun getLocalLocation(dri: DRI, context: PageNode?): String? =
        nodeIndex[dri]?.let { resolve(it, context) }
            ?: parentPageIndex[dri]?.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"
            }

    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 {
        return 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")
        }
    }

    public fun resolve(link: LinkJavadocListEntry, contextRoot: PageNode? = null, skipExtension: Boolean = true): String {
        return 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"
                JavadocContentKind.IndexPage -> it.dropLast(1) + "index-1"
                else -> it
            }
        }?.relativeTo(pathIndex[contextRoot].orEmpty())?.let { if (skipExtension) "$it.html" else it }.orEmpty()
    }

    override fun pathToRoot(from: PageNode): String {
        TODO("Not yet implemented")
    }

    override fun ancestors(node: PageNode): List {
        TODO("Not yet implemented")
    }

    override fun expectedLocationForDri(dri: DRI): String {
        if (dri.packageName?.isNotEmpty() == true && dri.classNames == null)
            return (dri.packageName?.split(".").orEmpty() + "package-summary").joinToString("/")

        return (dri.packageName?.split(".").orEmpty() +
                dri.classNames?.split(".").orEmpty() // Top-level methods will always be relocated which is fine
                ).joinToString("/")
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy