Merge remote-tracking branch 'upstream/java-layout-html-format' into devsite-with-java-layout-html
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt
index 1a0763f..5c740eb 100644
--- a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt
@@ -2,8 +2,13 @@
 
 import org.jetbrains.dokka.DocumentationModule
 import org.jetbrains.dokka.ExternalDocumentationLinkResolver.Companion.DOKKA_PARAM_PREFIX
+import org.jetbrains.dokka.InboundExternalLinkResolutionService
 import org.jetbrains.dokka.NodeKind
 import org.jetbrains.dokka.PackageListService
+import org.jetbrains.kotlin.descriptors.*
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
+import org.jetbrains.kotlin.resolve.descriptorUtil.isCompanionObject
+import org.jetbrains.kotlin.types.KotlinType
 
 class JavaLayoutHtmlPackageListService : PackageListService {
 
@@ -19,10 +24,91 @@
 
         return buildString {
             appendParam("format", "java-layout-html")
+            appendParam("mode", "kotlin")
             for (p in packages) {
                 appendln(p)
             }
         }
     }
 
+}
+
+class JavaLayoutHtmlInboundLinkResolutionService(private val paramMap: Map<String, List<String>>) : InboundExternalLinkResolutionService {
+    private fun getContainerPath(symbol: DeclarationDescriptor): String? {
+        return when (symbol) {
+            is PackageFragmentDescriptor -> symbol.fqName.asString() + "/"
+            is ClassifierDescriptor -> getContainerPath(symbol.findPackage()) + symbol.nameWithOuter() + ".html"
+            else -> null
+        }
+    }
+
+    private fun DeclarationDescriptor.findPackage(): PackageFragmentDescriptor =
+        generateSequence(this) { it.containingDeclaration }.filterIsInstance<PackageFragmentDescriptor>().first()
+
+    private fun ClassifierDescriptor.nameWithOuter(): String =
+        generateSequence(this) { it.containingDeclaration as? ClassifierDescriptor }
+            .toList().asReversed().joinToString(".") { it.name.asString() }
+
+    private fun getPagePath(symbol: DeclarationDescriptor): String? {
+        return when (symbol) {
+            is PackageFragmentDescriptor -> getContainerPath(symbol) + "package-summary.html"
+            is ClassifierDescriptor -> getContainerPath(symbol) + "#"
+            is FunctionDescriptor, is PropertyDescriptor -> getContainerPath(symbol.containingDeclaration!!) + "#" + symbol.signatureForAnchorUrlEncoded()
+            else -> null
+        }
+    }
+
+    private fun DeclarationDescriptor.signatureForAnchor(): String? {
+
+        fun ReceiverParameterDescriptor.extractReceiverName(): String {
+            var receiverClass: DeclarationDescriptor = type.constructor.declarationDescriptor!!
+            if (receiverClass.isCompanionObject()) {
+                receiverClass = receiverClass.containingDeclaration!!
+            } else if (receiverClass is TypeParameterDescriptor) {
+                val upperBoundClass = receiverClass.upperBounds.singleOrNull()?.constructor?.declarationDescriptor
+                if (upperBoundClass != null) {
+                    receiverClass = upperBoundClass
+                }
+            }
+
+            return receiverClass.name.asString()
+        }
+
+        fun KotlinType.qualifiedNameForSignature(): String {
+            val desc = constructor.declarationDescriptor
+            return desc?.fqNameUnsafe?.asString() ?: "<ERROR TYPE NAME>"
+        }
+
+        fun StringBuilder.appendReceiverAndCompanion(desc: CallableDescriptor) {
+            if (desc.containingDeclaration.isCompanionObject()) {
+                append("Companion.")
+            }
+            desc.extensionReceiverParameter?.let {
+                append("(")
+                append(it.extractReceiverName())
+                append(").")
+            }
+        }
+
+        return when(this) {
+            is FunctionDescriptor -> buildString {
+                appendReceiverAndCompanion(this@signatureForAnchor)
+                append(name.asString())
+                valueParameters.joinTo(this, prefix = "(", postfix = ")") {
+                    it.type.qualifiedNameForSignature()
+                }
+            }
+            is PropertyDescriptor -> buildString {
+                appendReceiverAndCompanion(this@signatureForAnchor)
+                append(name.asString())
+                append(":")
+                append(returnType?.qualifiedNameForSignature())
+            }
+            else -> null
+        }
+    }
+
+    private fun DeclarationDescriptor.signatureForAnchorUrlEncoded(): String? = signatureForAnchor()?.urlEncoded()
+
+    override fun getPath(symbol: DeclarationDescriptor) = getPagePath(symbol)
 }
\ No newline at end of file
diff --git a/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt b/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt
index 330e76a..0ac4854 100644
--- a/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt
+++ b/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt
@@ -4,6 +4,10 @@
 import com.google.inject.Singleton
 import com.intellij.psi.PsiMethod
 import com.intellij.util.io.*
+import org.jetbrains.dokka.Formats.FileGeneratorBasedFormatDescriptor
+import org.jetbrains.dokka.Formats.FormatDescriptor
+import org.jetbrains.dokka.Utilities.ServiceLocator
+import org.jetbrains.dokka.Utilities.lookup
 import org.jetbrains.kotlin.descriptors.*
 import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor
 import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
@@ -20,6 +24,7 @@
 import java.net.URLConnection
 import java.nio.file.Path
 import java.security.MessageDigest
+import kotlin.reflect.full.findAnnotation
 
 fun ByteArray.toHexString() = this.joinToString(separator = "") { "%02x".format(it) }
 
@@ -128,13 +133,22 @@
                 .map { (key, value) -> key to value }
                 .toMap()
 
-        val resolver = if (format == "javadoc") {
-            InboundExternalLinkResolutionService.Javadoc()
-        } else {
-            val linkExtension = paramsMap["linkExtension"]?.singleOrNull() ?:
-                    throw RuntimeException("Failed to parse package list from $packageListUrl")
-            InboundExternalLinkResolutionService.Dokka(linkExtension)
-        }
+
+        val defaultResolverDesc = services["dokka-default"]!!
+        val resolverDesc = services[format]
+                ?: defaultResolverDesc.takeIf { format in formatsWithDefaultResolver }
+                ?: defaultResolverDesc.also {
+                    logger.warn("Couldn't find InboundExternalLinkResolutionService(format = `$format`) for $link, using Dokka default")
+                }
+
+
+        val resolverClass = javaClass.classLoader.loadClass(resolverDesc.className).kotlin
+
+        val constructors = resolverClass.constructors
+
+        val constructor = constructors.singleOrNull()
+                ?: constructors.first { it.findAnnotation<Inject>() != null }
+        val resolver = constructor.call(paramsMap) as InboundExternalLinkResolutionService
 
         val rootInfo = ExternalDocumentationRoot(link.url, resolver, locations)
 
@@ -169,6 +183,15 @@
 
     companion object {
         const val DOKKA_PARAM_PREFIX = "\$dokka."
+        val services = ServiceLocator.allServices("inbound-link-resolver").associateBy { it.name }
+        private val formatsWithDefaultResolver =
+            ServiceLocator
+                .allServices("format")
+                .filter {
+                    val desc = ServiceLocator.lookup<FormatDescriptor>(it) as? FileGeneratorBasedFormatDescriptor
+                    desc?.generatorServiceClass == FileGenerator::class
+                }.map { it.name }
+                .toSet()
     }
 }
 
@@ -176,7 +199,7 @@
 interface InboundExternalLinkResolutionService {
     fun getPath(symbol: DeclarationDescriptor): String?
 
-    class Javadoc : InboundExternalLinkResolutionService {
+    class Javadoc(paramsMap: Map<String, List<String>>) : InboundExternalLinkResolutionService {
         override fun getPath(symbol: DeclarationDescriptor): String? {
             if (symbol is JavaClassDescriptor) {
                 return DescriptorUtils.getFqName(symbol).asString().replace(".", "/") + ".html"
@@ -200,7 +223,9 @@
         }
     }
 
-    class Dokka(val extension: String) : InboundExternalLinkResolutionService {
+    class Dokka(val paramsMap: Map<String, List<String>>) : InboundExternalLinkResolutionService {
+        val extension = paramsMap["linkExtension"]?.singleOrNull() ?: error("linkExtension not provided for Dokka resolver")
+
         override fun getPath(symbol: DeclarationDescriptor): String? {
             val leafElement = when (symbol) {
                 is CallableDescriptor, is TypeAliasDescriptor -> true
diff --git a/core/src/main/kotlin/Utilities/ServiceLocator.kt b/core/src/main/kotlin/Utilities/ServiceLocator.kt
index 71bfd21..83c4c65 100644
--- a/core/src/main/kotlin/Utilities/ServiceLocator.kt
+++ b/core/src/main/kotlin/Utilities/ServiceLocator.kt
@@ -14,12 +14,21 @@
 object ServiceLocator {
     fun <T : Any> lookup(clazz: Class<T>, category: String, implementationName: String): T {
         val descriptor = lookupDescriptor(category, implementationName)
+        return lookup(clazz, descriptor)
+    }
+
+    fun <T : Any> lookup(
+        clazz: Class<T>,
+        descriptor: ServiceDescriptor
+    ): T {
         val loadedClass = javaClass.classLoader.loadClass(descriptor.className)
         val constructor = loadedClass.constructors
-                .filter { it.parameterTypes.isEmpty() }
-                .firstOrNull() ?: throw ServiceLookupException("Class ${descriptor.className} has no corresponding constructor")
+            .filter { it.parameterTypes.isEmpty() }
+            .firstOrNull()
+                ?: throw ServiceLookupException("Class ${descriptor.className} has no corresponding constructor")
 
-        val implementationRawType: Any = if (constructor.parameterTypes.isEmpty()) constructor.newInstance() else constructor.newInstance(constructor)
+        val implementationRawType: Any =
+            if (constructor.parameterTypes.isEmpty()) constructor.newInstance() else constructor.newInstance(constructor)
 
         if (!clazz.isInstance(implementationRawType)) {
             throw ServiceLookupException("Class ${descriptor.className} is not a subtype of ${clazz.name}")
@@ -79,6 +88,7 @@
 }
 
 inline fun <reified T : Any> ServiceLocator.lookup(category: String, implementationName: String): T = lookup(T::class.java, category, implementationName)
+inline fun <reified T : Any> ServiceLocator.lookup(desc: ServiceDescriptor): T = lookup(T::class.java, desc)
 
 private val ZipEntry.fileName: String
     get() = name.substringAfterLast("/", name)
diff --git a/core/src/main/resources/dokka/inbound-link-resolver/dokka-default.properties b/core/src/main/resources/dokka/inbound-link-resolver/dokka-default.properties
new file mode 100644
index 0000000..c484a92
--- /dev/null
+++ b/core/src/main/resources/dokka/inbound-link-resolver/dokka-default.properties
@@ -0,0 +1,2 @@
+class=org.jetbrains.dokka.InboundExternalLinkResolutionService$Dokka
+description=Uses Dokka Default resolver
\ No newline at end of file
diff --git a/core/src/main/resources/dokka/inbound-link-resolver/java-layout-html.properties b/core/src/main/resources/dokka/inbound-link-resolver/java-layout-html.properties
new file mode 100644
index 0000000..3b61eab
--- /dev/null
+++ b/core/src/main/resources/dokka/inbound-link-resolver/java-layout-html.properties
@@ -0,0 +1,2 @@
+class=org.jetbrains.dokka.Formats.JavaLayoutHtmlInboundLinkResolutionService
+description=Resolver for JavaLayoutHtml
\ No newline at end of file
diff --git a/core/src/main/resources/dokka/inbound-link-resolver/javadoc.properties b/core/src/main/resources/dokka/inbound-link-resolver/javadoc.properties
new file mode 100644
index 0000000..0d5d7d1
--- /dev/null
+++ b/core/src/main/resources/dokka/inbound-link-resolver/javadoc.properties
@@ -0,0 +1,2 @@
+class=org.jetbrains.dokka.InboundExternalLinkResolutionService$Javadoc
+description=Uses Javadoc Default resolver
\ No newline at end of file
diff --git a/core/src/test/kotlin/format/JavaLayoutHtmlFormatTest.kt b/core/src/test/kotlin/format/JavaLayoutHtmlFormatTest.kt
index 8e7563c..c3b5ee9 100644
--- a/core/src/test/kotlin/format/JavaLayoutHtmlFormatTest.kt
+++ b/core/src/test/kotlin/format/JavaLayoutHtmlFormatTest.kt
@@ -1,8 +1,10 @@
 package org.jetbrains.dokka.tests
 
+import org.jetbrains.dokka.*
 import org.jetbrains.dokka.Formats.JavaLayoutHtmlFormatDescriptor
-import org.jetbrains.dokka.NodeKind
 import org.junit.Test
+import java.io.File
+import java.net.URL
 
 class JavaLayoutHtmlFormatTest : JavaLayoutHtmlFormatTestCase() {
     override val formatDescriptor = JavaLayoutHtmlFormatDescriptor()
@@ -55,4 +57,49 @@
     fun constJava() {
         verifyNode("ConstJava.java", noStdlibLink = true)
     }
+
+    @Test
+    fun inboundLinksInKotlinMode() {
+        val root = "./testdata/format/java-layout-html"
+
+        val options = DocumentationOptions(
+            "",
+            "java-layout-html",
+            sourceLinks = listOf(),
+            generateIndexPages = false,
+            noStdlibLink = true,
+            apiVersion = null,
+            languageVersion = null,
+            perPackageOptions = listOf(PackageOptionsImpl("foo", suppress = true)),
+            externalDocumentationLinks =
+            listOf(
+                DokkaConfiguration.ExternalDocumentationLink.Builder(
+                    URL("file:///"),
+                    File(root, "inboundLinksTestPackageList").toURI().toURL()
+                ).build()
+            )
+        )
+
+
+        val sourcePath = "$root/inboundLinksInKotlinMode.kt"
+        val documentation = DocumentationModule("test")
+
+        appendDocumentation(
+            documentation,
+            contentRootFromPath(sourcePath),
+            contentRootFromPath("$root/inboundLinksInKotlinMode.Dep.kt"),
+            withJdk = false,
+            withKotlinRuntime = false,
+            options = options
+        )
+        documentation.prepareForGeneration(options)
+
+        verifyModelOutput(documentation, ".html", sourcePath) { model, output ->
+            buildPagesAndReadInto(
+                model,
+                model.members.single { it.name == "bar" }.members,
+                output
+            )
+        }
+    }
 }
\ No newline at end of file
diff --git a/core/testdata/format/java-layout-html/inboundLinksInKotlinMode.Dep.kt b/core/testdata/format/java-layout-html/inboundLinksInKotlinMode.Dep.kt
new file mode 100644
index 0000000..610ebb2
--- /dev/null
+++ b/core/testdata/format/java-layout-html/inboundLinksInKotlinMode.Dep.kt
@@ -0,0 +1,23 @@
+package foo
+
+
+fun foobar() {
+
+}
+
+
+val v = 22
+
+class G {
+
+    fun oo() = ""
+
+    val og = 11
+
+    companion object {
+
+        fun dg() = "22"
+
+        val dv = 12
+    }
+}
\ No newline at end of file
diff --git a/core/testdata/format/java-layout-html/inboundLinksInKotlinMode.html b/core/testdata/format/java-layout-html/inboundLinksInKotlinMode.html
new file mode 100644
index 0000000..7025fc7
--- /dev/null
+++ b/core/testdata/format/java-layout-html/inboundLinksInKotlinMode.html
@@ -0,0 +1,80 @@
+<!-- File: /test/bar/X.html# -->
+<html>
+  <head>
+    <meta charset="UTF-8">
+  </head>
+  <body>
+    <h1>X</h1>
+    <pre><span class="keyword">class </span><span class="identifier">X</span></pre>
+    <table>
+      <tr>
+        <td><a href="#">bar.X</a></td>
+      </tr>
+    </table>
+    <p>See <a href="file:/foo/#foobar%28%29">foo.foobar</a>
+See <a href="file:/foo/#v%3Akotlin.Int">foo.v</a>
+See <a href="file:/foo/G.html#">foo.G</a>
+See <a href="file:/foo/G.html#oo%28%29">foo.G.oo</a>
+See <a href="file:/foo/G.html#og%3Akotlin.Int">foo.G.og</a>
+See <a href="file:/foo/G.Companion.html#Companion.dg%28%29">foo.G.Companion.dg</a>
+See <a href="file:/foo/G.Companion.html#Companion.dv%3Akotlin.Int">foo.G.Companion.dv</a></p>
+    <h2>Summary</h2>
+    <table>
+      <thead>
+        <tr>
+          <td>
+            <h3>Constructors</h3>
+          </td>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td>
+            <div><code><a href="#%3Cinit%3E%28%29">&lt;init&gt;</a>()</code></div>
+            <p>See <a href="file:/foo/#foobar%28%29">foo.foobar</a>
+See <a href="file:/foo/#v%3Akotlin.Int">foo.v</a>
+See <a href="file:/foo/G.html#">foo.G</a>
+See <a href="file:/foo/G.html#oo%28%29">foo.G.oo</a>
+See <a href="file:/foo/G.html#og%3Akotlin.Int">foo.G.og</a>
+See <a href="file:/foo/G.Companion.html#Companion.dg%28%29">foo.G.Companion.dg</a>
+See <a href="file:/foo/G.Companion.html#Companion.dv%3Akotlin.Int">foo.G.Companion.dv</a></p>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+    <table>
+      <thead>
+        <tr>
+          <td>
+            <h3>Functions</h3>
+          </td>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td><span class="identifier">String</span></td>
+          <td>
+            <div><code><a href="file:/foo/G.html#"><span class="identifier">G</span></a>.<a href="#%28foo.G%29.ext%28%29">ext</a>()</code></div>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+    <h2>Constructors</h2>
+    <div id="&lt;init&gt;()">
+      <h3>&lt;init&gt;</h3>
+      <pre><span class="identifier">X</span><span class="symbol">(</span><span class="symbol">)</span></pre>
+      <p>See <a href="file:/foo/#foobar%28%29">foo.foobar</a>
+See <a href="file:/foo/#v%3Akotlin.Int">foo.v</a>
+See <a href="file:/foo/G.html#">foo.G</a>
+See <a href="file:/foo/G.html#oo%28%29">foo.G.oo</a>
+See <a href="file:/foo/G.html#og%3Akotlin.Int">foo.G.og</a>
+See <a href="file:/foo/G.Companion.html#Companion.dg%28%29">foo.G.Companion.dg</a>
+See <a href="file:/foo/G.Companion.html#Companion.dv%3Akotlin.Int">foo.G.Companion.dv</a></p>
+    </div>
+    <h2>Functions</h2>
+    <div id="(foo.G).ext()">
+      <h3>ext</h3>
+      <pre><span class="keyword">fun </span><a href="file:/foo/G.html#"><span class="identifier">G</span></a><span class="symbol">.</span><span class="identifier">ext</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">String</span></pre>
+    </div>
+  </body>
+</html>
diff --git a/core/testdata/format/java-layout-html/inboundLinksInKotlinMode.kt b/core/testdata/format/java-layout-html/inboundLinksInKotlinMode.kt
new file mode 100644
index 0000000..6420d78
--- /dev/null
+++ b/core/testdata/format/java-layout-html/inboundLinksInKotlinMode.kt
@@ -0,0 +1,16 @@
+package bar
+
+/**
+ * See [foo.foobar]
+ * See [foo.v]
+ * See [foo.G]
+ * See [foo.G.oo]
+ * See [foo.G.og]
+ * See [foo.G.Companion.dg]
+ * See [foo.G.Companion.dv]
+ */
+class X {
+
+    fun (foo.G).ext() = this.oo()
+}
+
diff --git a/core/testdata/format/java-layout-html/inboundLinksTestPackageList b/core/testdata/format/java-layout-html/inboundLinksTestPackageList
new file mode 100644
index 0000000..64d25c3
--- /dev/null
+++ b/core/testdata/format/java-layout-html/inboundLinksTestPackageList
@@ -0,0 +1,3 @@
+$dokka.format:java-layout-html
+$dokka.mode:kotlin
+foo
diff --git a/core/testdata/format/java-layout-html/topLevel.package-summary.html b/core/testdata/format/java-layout-html/topLevel.package-summary.html
index 791c371..03665c6 100644
--- a/core/testdata/format/java-layout-html/topLevel.package-summary.html
+++ b/core/testdata/format/java-layout-html/topLevel.package-summary.html
@@ -14,6 +14,17 @@
         </tr>
       </tbody>
     </table>
+    <h2>Top-level constants summary</h2>
+    <table>
+      <tbody>
+        <tr>
+          <td><span class="keyword">const</span> <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html"><span class="identifier">String</span></a></td>
+          <td>
+            <div><code><a href="#topLevelConst%3Akotlin.String">topLevelConst</a></code></div>
+          </td>
+        </tr>
+      </tbody>
+    </table>
     <h2>Top-level functions summary</h2>
     <table>
       <tbody>
@@ -41,12 +52,6 @@
           </td>
         </tr>
         <tr>
-          <td><span class="keyword">const</span> <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html"><span class="identifier">String</span></a></td>
-          <td>
-            <div><code><a href="#topLevelConst%3Akotlin.String">topLevelConst</a></code></div>
-          </td>
-        </tr>
-        <tr>
           <td><a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html"><span class="identifier">String</span></a></td>
           <td>
             <div><code><a href="#topLevelGetVal%3Akotlin.String">topLevelGetVal</a></code></div>
@@ -60,6 +65,12 @@
         </tr>
       </tbody>
     </table>
+    <h2>Top-level constants</h2>
+    <div id="topLevelConst:kotlin.String">
+      <h3>topLevelConst</h3>
+      <pre><span class="keyword">const</span> <span class="keyword">val </span><span class="identifier">topLevelConst</span><span class="symbol">: </span><a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html"><span class="identifier">String</span></a></pre>
+      <pre>Value: <code>&quot;&quot;</code></pre>
+    </div>
     <h2>Top-level functions</h2>
     <div id="topLevelFun()">
       <h3>topLevelFun</h3>
@@ -74,10 +85,6 @@
       <h3>topLevelVal</h3>
       <pre><span class="keyword">val </span><span class="identifier">topLevelVal</span><span class="symbol">: </span><a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html"><span class="identifier">String</span></a></pre>
     </div>
-    <div id="topLevelConst:kotlin.String">
-      <h3>topLevelConst</h3>
-      <pre><span class="keyword">const</span> <span class="keyword">val </span><span class="identifier">topLevelConst</span><span class="symbol">: </span><a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html"><span class="identifier">String</span></a></pre>
-    </div>
     <div id="topLevelGetVal:kotlin.String">
       <h3>topLevelGetVal</h3>
       <pre><span class="keyword">val </span><span class="identifier">topLevelGetVal</span><span class="symbol">: </span><a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html"><span class="identifier">String</span></a></pre>