Split JavaLayoutHtml to separate files
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormat.kt b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormat.kt
new file mode 100644
index 0000000..1db979d
--- /dev/null
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormat.kt
@@ -0,0 +1,111 @@
+package org.jetbrains.dokka.Formats
+
+import com.google.inject.Binder
+import kotlinx.html.*
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.Utilities.bind
+import org.jetbrains.dokka.Utilities.lazyBind
+import org.jetbrains.dokka.Utilities.toOptional
+import org.jetbrains.dokka.Utilities.toType
+import java.net.URI
+import java.net.URLEncoder
+import kotlin.reflect.KClass
+
+
+abstract class JavaLayoutHtmlFormatDescriptorBase : FormatDescriptor, DefaultAnalysisComponent {
+
+    override fun configureOutput(binder: Binder): Unit = with(binder) {
+        bind<Generator>() toType generatorServiceClass
+        bind<LanguageService>() toType languageServiceClass
+        bind<JavaLayoutHtmlTemplateService>() toType templateServiceClass
+        bind<JavaLayoutHtmlUriProvider>() toType generatorServiceClass
+        lazyBind<JavaLayoutHtmlFormatOutlineFactoryService>() toOptional outlineFactoryClass
+        bind<PackageListService>() toType packageListServiceClass
+    }
+
+    val generatorServiceClass = JavaLayoutHtmlFormatGenerator::class
+    abstract val languageServiceClass: KClass<out LanguageService>
+    abstract val templateServiceClass: KClass<out JavaLayoutHtmlTemplateService>
+    abstract val outlineFactoryClass: KClass<out JavaLayoutHtmlFormatOutlineFactoryService>?
+    abstract val packageListServiceClass: KClass<out PackageListService>
+}
+
+class JavaLayoutHtmlFormatDescriptor : JavaLayoutHtmlFormatDescriptorBase(), DefaultAnalysisComponentServices by KotlinAsKotlin {
+    override val packageListServiceClass: KClass<out PackageListService>
+        get() = TODO("not implemented")
+    override val languageServiceClass = KotlinLanguageService::class
+    override val templateServiceClass = JavaLayoutHtmlTemplateService.Default::class
+    override val outlineFactoryClass = null
+}
+
+interface JavaLayoutHtmlFormatOutlineFactoryService {
+    fun generateOutlines(outputProvider: (URI) -> Appendable, nodes: Iterable<DocumentationNode>)
+}
+
+
+interface JavaLayoutHtmlUriProvider {
+    fun tryGetContainerUri(node: DocumentationNode): URI?
+    fun tryGetMainUri(node: DocumentationNode): URI?
+    fun containerUri(node: DocumentationNode): URI = tryGetContainerUri(node) ?: error("Unsupported ${node.kind}")
+    fun mainUri(node: DocumentationNode): URI = tryGetMainUri(node) ?: error("Unsupported ${node.kind}")
+
+    fun linkTo(to: DocumentationNode, from: URI): String {
+        return mainUri(to).relativeTo(from).toString()
+    }
+
+    fun mainUriOrWarn(node: DocumentationNode): URI? = tryGetMainUri(node) ?: (null).also {
+        AssertionError("Not implemented mainUri for ${node.kind}").printStackTrace()
+    }
+}
+
+
+interface JavaLayoutHtmlTemplateService {
+    fun composePage(
+            nodes: List<DocumentationNode>,
+            tagConsumer: TagConsumer<Appendable>,
+            headContent: HEAD.() -> Unit,
+            bodyContent: BODY.() -> Unit
+    )
+
+    class Default : JavaLayoutHtmlTemplateService {
+        override fun composePage(
+                nodes: List<DocumentationNode>,
+                tagConsumer: TagConsumer<Appendable>,
+                headContent: HEAD.() -> Unit,
+                bodyContent: BODY.() -> Unit
+        ) {
+            tagConsumer.html {
+                head {
+                    meta(charset = "UTF-8")
+                    headContent()
+                }
+                body(block = bodyContent)
+            }
+        }
+    }
+}
+
+
+fun DocumentationNode.signatureForAnchor(logger: DokkaLogger): String = when (kind) {
+    NodeKind.Function, NodeKind.Constructor -> buildString {
+        detailOrNull(NodeKind.Receiver)?.let {
+            append("(")
+            append(it.detail(NodeKind.Type).qualifiedNameFromType())
+            append(").")
+        }
+        append(name)
+        details(NodeKind.Parameter).joinTo(this, prefix = "(", postfix = ")") { it.detail(NodeKind.Type).qualifiedNameFromType() }
+    }
+    NodeKind.Property ->
+        "$name:${detail(NodeKind.Type).qualifiedNameFromType()}"
+    NodeKind.TypeParameter, NodeKind.Parameter -> owner!!.signatureForAnchor(logger) + "/" + name
+    else -> "Not implemented signatureForAnchor $this".also { logger.warn(it) }
+}
+
+
+fun String.urlEncoded(): String = URLEncoder.encode(this, "UTF-8")
+
+fun DocumentationNode.classNodeNameWithOuterClass(): String {
+    assert(kind in NodeKind.classLike)
+    return path.dropWhile { it.kind == NodeKind.Package || it.kind == NodeKind.Module }.joinToString(separator = ".") { it.name }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt
similarity index 60%
rename from core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt
rename to core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt
index 446ff9e..84778d0 100644
--- a/core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt
@@ -1,8 +1,5 @@
 package org.jetbrains.dokka.Formats
 
-import com.google.inject.Binder
-import com.google.inject.Inject
-import com.google.inject.name.Named
 import kotlinx.html.*
 import kotlinx.html.Entities.nbsp
 import kotlinx.html.stream.appendHTML
@@ -10,73 +7,10 @@
 import org.jetbrains.dokka.LanguageService.RenderMode.FULL
 import org.jetbrains.dokka.LanguageService.RenderMode.SUMMARY
 import org.jetbrains.dokka.NodeKind.Companion.classLike
-import org.jetbrains.dokka.NodeKind.Companion.memberLike
-import org.jetbrains.dokka.Utilities.bind
-import org.jetbrains.dokka.Utilities.lazyBind
-import org.jetbrains.dokka.Utilities.toOptional
-import org.jetbrains.dokka.Utilities.toType
-import org.jetbrains.kotlin.preprocessor.mkdirsOrFail
-import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
-import java.io.BufferedWriter
-import java.io.File
 import java.net.URI
 import java.net.URLEncoder
-import kotlin.reflect.KClass
 
 
-abstract class JavaLayoutHtmlFormatDescriptorBase : FormatDescriptor, DefaultAnalysisComponent {
-
-    override fun configureOutput(binder: Binder): Unit = with(binder) {
-        bind<Generator>() toType generatorServiceClass
-        bind<LanguageService>() toType languageServiceClass
-        bind<JavaLayoutHtmlTemplateService>() toType templateServiceClass
-        bind<JavaLayoutHtmlUriProvider>() toType generatorServiceClass
-        lazyBind<JavaLayoutHtmlFormatOutlineFactoryService>() toOptional outlineFactoryClass
-    }
-
-    val generatorServiceClass = JavaLayoutHtmlFormatGenerator::class
-    abstract val languageServiceClass: KClass<out LanguageService>
-    abstract val templateServiceClass: KClass<out JavaLayoutHtmlTemplateService>
-    abstract val outlineFactoryClass: KClass<out JavaLayoutHtmlFormatOutlineFactoryService>?
-}
-
-class JavaLayoutHtmlFormatDescriptor : JavaLayoutHtmlFormatDescriptorBase(), DefaultAnalysisComponentServices by KotlinAsKotlin {
-    override val languageServiceClass = KotlinLanguageService::class
-    override val templateServiceClass = JavaLayoutHtmlTemplateService.Default::class
-    override val outlineFactoryClass = null
-}
-
-
-interface JavaLayoutHtmlTemplateService {
-    fun composePage(
-            nodes: List<DocumentationNode>,
-            tagConsumer: TagConsumer<Appendable>,
-            headContent: HEAD.() -> Unit,
-            bodyContent: BODY.() -> Unit
-    )
-
-    class Default : JavaLayoutHtmlTemplateService {
-        override fun composePage(
-                nodes: List<DocumentationNode>,
-                tagConsumer: TagConsumer<Appendable>,
-                headContent: HEAD.() -> Unit,
-                bodyContent: BODY.() -> Unit
-        ) {
-            tagConsumer.html {
-                head {
-                    meta(charset = "UTF-8")
-                    headContent()
-                }
-                body(block = bodyContent)
-            }
-        }
-    }
-}
-
-interface JavaLayoutHtmlFormatOutlineFactoryService {
-    fun generateOutlines(outputProvider: (URI) -> Appendable, nodes: Iterable<DocumentationNode>)
-}
-
 class JavaLayoutHtmlFormatOutputBuilder(
         val output: Appendable,
         val languageService: LanguageService,
@@ -471,192 +405,4 @@
             is ContentBlock -> appendContent(content.children)
         }
     }
-}
-
-
-interface JavaLayoutHtmlUriProvider {
-    fun tryGetContainerUri(node: DocumentationNode): URI?
-    fun tryGetMainUri(node: DocumentationNode): URI?
-    fun containerUri(node: DocumentationNode): URI = tryGetContainerUri(node) ?: error("Unsupported ${node.kind}")
-    fun mainUri(node: DocumentationNode): URI = tryGetMainUri(node) ?: error("Unsupported ${node.kind}")
-
-    fun linkTo(to: DocumentationNode, from: URI): String {
-        return mainUri(to).relativeTo(from).toString()
-    }
-
-    fun mainUriOrWarn(node: DocumentationNode): URI? = tryGetMainUri(node) ?: (null).also {
-        AssertionError("Not implemented mainUri for ${node.kind}").printStackTrace()
-    }
-}
-
-class JavaLayoutHtmlFormatGenerator @Inject constructor(
-        @Named("outputDir") val root: File,
-        val languageService: LanguageService,
-        val templateService: JavaLayoutHtmlTemplateService,
-        val logger: DokkaLogger
-) : Generator, JavaLayoutHtmlUriProvider {
-
-    @set:Inject(optional = true)
-    var outlineFactoryService: JavaLayoutHtmlFormatOutlineFactoryService? = null
-
-    fun createOutputBuilderForNode(node: DocumentationNode, output: Appendable)
-            = JavaLayoutHtmlFormatOutputBuilder(output, languageService, this, templateService, logger, mainUri(node))
-
-    override fun tryGetContainerUri(node: DocumentationNode): URI? {
-        return when (node.kind) {
-            NodeKind.Module -> URI("/").resolve(node.name + "/")
-            NodeKind.Package -> tryGetContainerUri(node.owner!!)?.resolve(node.name.replace('.', '/') + '/')
-            in classLike -> tryGetContainerUri(node.owner!!)?.resolve("${node.classNodeNameWithOuterClass()}.html")
-            else -> null
-        }
-    }
-
-    override fun tryGetMainUri(node: DocumentationNode): URI? {
-        return when (node.kind) {
-            NodeKind.Package -> tryGetContainerUri(node)?.resolve("package-summary.html")
-            in classLike -> tryGetContainerUri(node)?.resolve("#")
-            in memberLike -> {
-                val owner = if (node.owner?.kind != NodeKind.ExternalClass) node.owner else node.owner?.owner
-                tryGetMainUri(owner!!)?.resolveInPage(node)
-            }
-            NodeKind.TypeParameter, NodeKind.Parameter -> node.path.asReversed().drop(1).firstNotNullResult(this::tryGetMainUri)?.resolveInPage(node)
-            NodeKind.AllTypes -> tryGetContainerUri(node.owner!!)?.resolve("classes.html")
-            else -> null
-        }
-    }
-
-    fun URI.resolveInPage(node: DocumentationNode): URI = resolve("#${node.signatureUrlEncoded(logger)}")
-
-    fun buildClass(node: DocumentationNode, parentDir: File) {
-        val fileForClass = parentDir.resolve(node.classNodeNameWithOuterClass() + ".html")
-        fileForClass.bufferedWriter().use {
-            createOutputBuilderForNode(node, it).appendClassLike(node)
-        }
-        for (memberClass in node.members.filter { it.kind in classLike }) {
-            buildClass(memberClass, parentDir)
-        }
-    }
-
-    fun buildPackage(node: DocumentationNode, parentDir: File) {
-        assert(node.kind == NodeKind.Package)
-        val members = node.members
-        val directoryForPackage = parentDir.resolve(node.name.replace('.', File.separatorChar))
-        directoryForPackage.mkdirsOrFail()
-
-        directoryForPackage.resolve("package-summary.html").bufferedWriter().use {
-            createOutputBuilderForNode(node, it).appendPackage(node)
-        }
-
-        members.filter { it.kind in classLike }.forEach {
-            buildClass(it, directoryForPackage)
-        }
-    }
-
-    fun buildClassIndex(node: DocumentationNode, parentDir: File) {
-        val file = parentDir.resolve("classes.html")
-        file.bufferedWriter().use {
-            createOutputBuilderForNode(node, it).generateClassesIndex(node)
-        }
-    }
-
-    fun buildPackageIndex(nodes: List<DocumentationNode>, parentDir: File) {
-        val file = parentDir.resolve("packages.html")
-        file.bufferedWriter().use {
-            JavaLayoutHtmlFormatOutputBuilder(it, languageService, this, templateService, logger, containerUri(nodes.first().owner!!).resolve("packages.html"))
-                    .generatePackageIndex(nodes)
-        }
-    }
-
-    override fun buildPages(nodes: Iterable<DocumentationNode>) {
-        val module = nodes.single()
-
-        val moduleRoot = root.resolve(module.name)
-        val packages = module.members.filter { it.kind == NodeKind.Package }
-        packages.forEach { buildPackage(it, moduleRoot) }
-
-        buildClassIndex(module.members.single { it.kind == NodeKind.AllTypes }, moduleRoot)
-        buildPackageIndex(packages, moduleRoot)
-    }
-
-    override fun buildOutlines(nodes: Iterable<DocumentationNode>) {
-        val uriToWriter = mutableMapOf<URI, BufferedWriter>()
-
-        fun provideOutput(uri: URI): BufferedWriter {
-            val normalized = uri.normalize()
-            uriToWriter[normalized]?.let { return it }
-            val file = root.resolve(normalized.path.removePrefix("/"))
-            val writer = file.bufferedWriter()
-            uriToWriter[normalized] = writer
-            return writer
-        }
-
-        outlineFactoryService?.generateOutlines(::provideOutput, nodes)
-
-        uriToWriter.values.forEach { it.close() }
-    }
-
-    override fun buildSupportFiles() {}
-
-    override fun buildPackageList(nodes: Iterable<DocumentationNode>) {
-
-    }
-}
-
-fun DocumentationNode.signatureForAnchor(logger: DokkaLogger): String = when (kind) {
-    NodeKind.Function, NodeKind.Constructor -> buildString {
-        detailOrNull(NodeKind.Receiver)?.let {
-            append("(")
-            append(it.detail(NodeKind.Type).qualifiedNameFromType())
-            append(").")
-        }
-        append(name)
-        details(NodeKind.Parameter).joinTo(this, prefix = "(", postfix = ")") { it.detail(NodeKind.Type).qualifiedNameFromType() }
-    }
-    NodeKind.Property ->
-        "$name:${detail(NodeKind.Type).qualifiedNameFromType()}"
-    NodeKind.TypeParameter, NodeKind.Parameter -> owner!!.signatureForAnchor(logger) + "/" + name
-    else -> "Not implemented signatureForAnchor $this".also { logger.warn(it) }
-}
-
-fun DocumentationNode.signatureUrlEncoded(logger: DokkaLogger) = URLEncoder.encode(signatureForAnchor(logger), "UTF-8")
-
-fun DocumentationNode.classNodeNameWithOuterClass(): String {
-    assert(kind in NodeKind.classLike)
-    return path.dropWhile { it.kind == NodeKind.Package || it.kind == NodeKind.Module }.joinToString(separator = ".") { it.name }
-}
-
-fun URI.relativeTo(uri: URI): URI {
-    // Normalize paths to remove . and .. segments
-    val base = uri.normalize()
-    val child = this.normalize()
-
-    fun StringBuilder.appendRelativePath() {
-        // Split paths into segments
-        var bParts = base.path.split('/').dropLastWhile { it.isEmpty() }
-        val cParts = child.path.split('/').dropLastWhile { it.isEmpty() }
-
-        // Discard trailing segment of base path
-        if (bParts.isNotEmpty() && !base.path.endsWith("/")) {
-            bParts = bParts.dropLast(1)
-        }
-
-        // Compute common prefix
-        val commonPartsSize = bParts.zip(cParts).count { (basePart, childPart) -> basePart == childPart }
-        bParts.drop(commonPartsSize).joinTo(this, separator = "") { "../" }
-        cParts.drop(commonPartsSize).joinTo(this, separator = "/")
-    }
-
-    return URI.create(buildString {
-        if (base.path != child.path) {
-            appendRelativePath()
-        }
-        child.rawQuery?.let {
-            append("?")
-            append(it)
-        }
-        child.rawFragment?.let {
-            append("#")
-            append(it)
-        }
-    })
 }
\ No newline at end of file
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlGenerator.kt b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlGenerator.kt
new file mode 100644
index 0000000..fcb26c7
--- /dev/null
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlGenerator.kt
@@ -0,0 +1,124 @@
+package org.jetbrains.dokka.Formats
+
+import com.google.inject.Inject
+import com.google.inject.name.Named
+import org.jetbrains.dokka.*
+import org.jetbrains.kotlin.preprocessor.mkdirsOrFail
+import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
+import java.io.BufferedWriter
+import java.io.File
+import java.net.URI
+
+
+class JavaLayoutHtmlFormatGenerator @Inject constructor(
+        @Named("outputDir") val root: File,
+        val languageService: LanguageService,
+        val templateService: JavaLayoutHtmlTemplateService,
+        val logger: DokkaLogger
+) : Generator, JavaLayoutHtmlUriProvider {
+
+    @set:Inject(optional = true)
+    var outlineFactoryService: JavaLayoutHtmlFormatOutlineFactoryService? = null
+
+    fun createOutputBuilderForNode(node: DocumentationNode, output: Appendable)
+            = JavaLayoutHtmlFormatOutputBuilder(output, languageService, this, templateService, logger, mainUri(node))
+
+    override fun tryGetContainerUri(node: DocumentationNode): URI? {
+        return when (node.kind) {
+            NodeKind.Module -> URI("/").resolve(node.name + "/")
+            NodeKind.Package -> tryGetContainerUri(node.owner!!)?.resolve(node.name.replace('.', '/') + '/')
+            in NodeKind.classLike -> tryGetContainerUri(node.owner!!)?.resolve("${node.classNodeNameWithOuterClass()}.html")
+            else -> null
+        }
+    }
+
+    override fun tryGetMainUri(node: DocumentationNode): URI? {
+        return when (node.kind) {
+            NodeKind.Package -> tryGetContainerUri(node)?.resolve("package-summary.html")
+            in NodeKind.classLike -> tryGetContainerUri(node)?.resolve("#")
+            in NodeKind.memberLike -> {
+                val owner = if (node.owner?.kind != NodeKind.ExternalClass) node.owner else node.owner?.owner
+                tryGetMainUri(owner!!)?.resolveInPage(node)
+            }
+            NodeKind.TypeParameter, NodeKind.Parameter -> node.path.asReversed().drop(1).firstNotNullResult(this::tryGetMainUri)?.resolveInPage(node)
+            NodeKind.AllTypes -> tryGetContainerUri(node.owner!!)?.resolve("classes.html")
+            else -> null
+        }
+    }
+
+    fun URI.resolveInPage(node: DocumentationNode): URI = resolve("#${node.signatureForAnchor(logger).urlEncoded()}")
+
+    fun buildClass(node: DocumentationNode, parentDir: File) {
+        val fileForClass = parentDir.resolve(node.classNodeNameWithOuterClass() + ".html")
+        fileForClass.bufferedWriter().use {
+            createOutputBuilderForNode(node, it).appendClassLike(node)
+        }
+        for (memberClass in node.members.filter { it.kind in NodeKind.classLike }) {
+            buildClass(memberClass, parentDir)
+        }
+    }
+
+    fun buildPackage(node: DocumentationNode, parentDir: File) {
+        assert(node.kind == NodeKind.Package)
+        val members = node.members
+        val directoryForPackage = parentDir.resolve(node.name.replace('.', File.separatorChar))
+        directoryForPackage.mkdirsOrFail()
+
+        directoryForPackage.resolve("package-summary.html").bufferedWriter().use {
+            createOutputBuilderForNode(node, it).appendPackage(node)
+        }
+
+        members.filter { it.kind in NodeKind.classLike }.forEach {
+            buildClass(it, directoryForPackage)
+        }
+    }
+
+    fun buildClassIndex(node: DocumentationNode, parentDir: File) {
+        val file = parentDir.resolve("classes.html")
+        file.bufferedWriter().use {
+            createOutputBuilderForNode(node, it).generateClassesIndex(node)
+        }
+    }
+
+    fun buildPackageIndex(nodes: List<DocumentationNode>, parentDir: File) {
+        val file = parentDir.resolve("packages.html")
+        file.bufferedWriter().use {
+            JavaLayoutHtmlFormatOutputBuilder(it, languageService, this, templateService, logger, containerUri(nodes.first().owner!!).resolve("packages.html"))
+                    .generatePackageIndex(nodes)
+        }
+    }
+
+    override fun buildPages(nodes: Iterable<DocumentationNode>) {
+        val module = nodes.single()
+
+        val moduleRoot = root.resolve(module.name)
+        val packages = module.members.filter { it.kind == NodeKind.Package }
+        packages.forEach { buildPackage(it, moduleRoot) }
+
+        buildClassIndex(module.members.single { it.kind == NodeKind.AllTypes }, moduleRoot)
+        buildPackageIndex(packages, moduleRoot)
+    }
+
+    override fun buildOutlines(nodes: Iterable<DocumentationNode>) {
+        val uriToWriter = mutableMapOf<URI, BufferedWriter>()
+
+        fun provideOutput(uri: URI): BufferedWriter {
+            val normalized = uri.normalize()
+            uriToWriter[normalized]?.let { return it }
+            val file = root.resolve(normalized.path.removePrefix("/"))
+            val writer = file.bufferedWriter()
+            uriToWriter[normalized] = writer
+            return writer
+        }
+
+        outlineFactoryService?.generateOutlines(::provideOutput, nodes)
+
+        uriToWriter.values.forEach { it.close() }
+    }
+
+    override fun buildSupportFiles() {}
+
+    override fun buildPackageList(nodes: Iterable<DocumentationNode>) {
+
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/Utilities/Uri.kt b/core/src/main/kotlin/Utilities/Uri.kt
new file mode 100644
index 0000000..5b52018
--- /dev/null
+++ b/core/src/main/kotlin/Utilities/Uri.kt
@@ -0,0 +1,40 @@
+package org.jetbrains.dokka
+
+import java.net.URI
+
+
+fun URI.relativeTo(uri: URI): URI {
+    // Normalize paths to remove . and .. segments
+    val base = uri.normalize()
+    val child = this.normalize()
+
+    fun StringBuilder.appendRelativePath() {
+        // Split paths into segments
+        var bParts = base.path.split('/').dropLastWhile { it.isEmpty() }
+        val cParts = child.path.split('/').dropLastWhile { it.isEmpty() }
+
+        // Discard trailing segment of base path
+        if (bParts.isNotEmpty() && !base.path.endsWith("/")) {
+            bParts = bParts.dropLast(1)
+        }
+
+        // Compute common prefix
+        val commonPartsSize = bParts.zip(cParts).count { (basePart, childPart) -> basePart == childPart }
+        bParts.drop(commonPartsSize).joinTo(this, separator = "") { "../" }
+        cParts.drop(commonPartsSize).joinTo(this, separator = "/")
+    }
+
+    return URI.create(buildString {
+        if (base.path != child.path) {
+            appendRelativePath()
+        }
+        child.rawQuery?.let {
+            append("?")
+            append(it)
+        }
+        child.rawFragment?.let {
+            append("#")
+            append(it)
+        }
+    })
+}
\ No newline at end of file