Merge branch 'java-layout-html-format' into devsite-with-java-layout-html
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..5148097
--- /dev/null
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormat.kt
@@ -0,0 +1,131 @@
+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
+        bind<JavaLayoutHtmlFormatOutputBuilderFactory>() toType outputBuilderFactoryClass
+    }
+
+    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>
+    abstract val outputBuilderFactoryClass: KClass<out JavaLayoutHtmlFormatOutputBuilderFactory>
+}
+
+class JavaLayoutHtmlFormatDescriptor : JavaLayoutHtmlFormatDescriptorBase(), DefaultAnalysisComponentServices by KotlinAsKotlin {
+    override val outputBuilderFactoryClass: KClass<out JavaLayoutHtmlFormatOutputBuilderFactory> = JavaLayoutHtmlFormatOutputBuilderFactoryImpl::class
+    override val packageListServiceClass: KClass<out PackageListService> = JavaLayoutHtmlPackageListService::class
+    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)
+            }
+        }
+    }
+}
+
+val DocumentationNode.companion get() = members(NodeKind.Object).find { it.details(NodeKind.Modifier).any { it.name == "companion" } }
+
+fun DocumentationNode.signatureForAnchor(logger: DokkaLogger): String {
+
+    fun StringBuilder.appendReceiverIfSo() {
+        detailOrNull(NodeKind.Receiver)?.let {
+            append("(")
+            append(it.detail(NodeKind.Type).qualifiedNameFromType())
+            append(").")
+        }
+    }
+
+    return when (kind) {
+        NodeKind.Function, NodeKind.Constructor, NodeKind.CompanionObjectFunction -> buildString {
+            if (kind == NodeKind.CompanionObjectFunction) {
+                append("Companion.")
+            }
+            appendReceiverIfSo()
+            append(name)
+            details(NodeKind.Parameter).joinTo(this, prefix = "(", postfix = ")") { it.detail(NodeKind.Type).qualifiedNameFromType() }
+        }
+        NodeKind.Property, NodeKind.CompanionObjectProperty -> buildString {
+            if (kind == NodeKind.CompanionObjectProperty) {
+                append("Companion.")
+            }
+            appendReceiverIfSo()
+            append(name)
+            append(":")
+            append(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/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt
new file mode 100644
index 0000000..0c95a41
--- /dev/null
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlFormatOutputBuilder.kt
@@ -0,0 +1,545 @@
+package org.jetbrains.dokka.Formats
+
+import kotlinx.html.*
+import kotlinx.html.Entities.nbsp
+import kotlinx.html.stream.appendHTML
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.LanguageService.RenderMode.FULL
+import org.jetbrains.dokka.LanguageService.RenderMode.SUMMARY
+import org.jetbrains.dokka.NodeKind.Companion.classLike
+import java.net.URI
+import javax.inject.Inject
+
+
+open class JavaLayoutHtmlFormatOutputBuilder(
+        val output: Appendable,
+        val languageService: LanguageService,
+        val uriProvider: JavaLayoutHtmlUriProvider,
+        val templateService: JavaLayoutHtmlTemplateService,
+        val logger: DokkaLogger,
+        val uri: URI
+) {
+
+    val htmlConsumer = output.appendHTML()
+
+    val contentToHtmlBuilder = ContentToHtmlBuilder(uriProvider, uri)
+
+    open fun <T> FlowContent.summaryNodeGroup(nodes: Iterable<T>, header: String, headerAsRow: Boolean = false, row: TBODY.(T) -> Unit) {
+        if (nodes.none()) return
+        if (!headerAsRow) {
+            h2 { +header }
+        }
+        table {
+            if (headerAsRow) thead { tr { td { h3 { +header } } } }
+            tbody {
+                nodes.forEach { node ->
+                    row(node)
+                }
+            }
+        }
+    }
+
+    fun FlowContent.metaMarkup(content: ContentNode) = with(contentToHtmlBuilder) {
+        appendContent(content)
+    }
+
+    fun FlowContent.metaMarkup(content: List<ContentNode>) = with(contentToHtmlBuilder) {
+        appendContent(content)
+    }
+
+    open fun TBODY.classLikeRow(node: DocumentationNode) = tr {
+        td { a(href = uriProvider.linkTo(node, uri)) { +node.simpleName() } }
+        td { metaMarkup(node.summary) }
+    }
+
+    fun FlowContent.modifiers(node: DocumentationNode) {
+        for (modifier in node.details(NodeKind.Modifier)) {
+            renderedSignature(modifier, SUMMARY)
+        }
+    }
+
+    fun FlowContent.shortFunctionParametersList(func: DocumentationNode) {
+        val params = func.details(NodeKind.Parameter)
+                .map { languageService.render(it, FULL) }
+                .run {
+                    drop(1).fold(listOfNotNull(firstOrNull())) { acc, node ->
+                        acc + ContentText(", ") + node
+                    }
+                }
+        metaMarkup(listOf(ContentText("(")) + params + listOf(ContentText(")")))
+    }
+
+
+    open fun TBODY.functionLikeSummaryRow(node: DocumentationNode) = tr {
+        if (node.kind != NodeKind.Constructor) {
+            td {
+                modifiers(node)
+                renderedSignature(node.detail(NodeKind.Type), SUMMARY)
+            }
+        }
+        td {
+            div {
+                code {
+                    val receiver = node.detailOrNull(NodeKind.Receiver)
+                    if (receiver != null) {
+                        renderedSignature(receiver.detail(NodeKind.Type), SUMMARY)
+                        +"."
+                    }
+                    a(href = uriProvider.linkTo(node, uri)) { +node.name }
+                    shortFunctionParametersList(node)
+                }
+            }
+
+            metaMarkup(node.summary)
+        }
+    }
+
+    open fun TBODY.propertyLikeSummaryRow(node: DocumentationNode) = tr {
+        td {
+            modifiers(node)
+            renderedSignature(node.detail(NodeKind.Type), SUMMARY)
+        }
+        td {
+            div {
+                code {
+                    a(href = uriProvider.linkTo(node, uri)) { +node.name }
+                }
+            }
+
+            metaMarkup(node.summary)
+        }
+    }
+
+    open fun TBODY.nestedClassSummaryRow(node: DocumentationNode) = tr {
+        td {
+            modifiers(node)
+        }
+        td {
+            div {
+                code {
+                    a(href = uriProvider.linkTo(node, uri)) { +node.name }
+                }
+            }
+
+            metaMarkup(node.summary)
+        }
+    }
+
+    open fun TBODY.inheritRow(entry: Map.Entry<DocumentationNode, List<DocumentationNode>>, summaryRow: TBODY.(DocumentationNode) -> Unit) = tr {
+        td {
+            val (from, nodes) = entry
+            +"From class "
+            a(href = uriProvider.linkTo(from.owner!!, uri)) { +from.qualifiedName() }
+            table {
+                tbody {
+                    for (node in nodes) {
+                        summaryRow(node)
+                    }
+                }
+            }
+        }
+    }
+
+    open fun TBODY.extensionRow(entry: Map.Entry<DocumentationNode, List<DocumentationNode>>, summaryRow: TBODY.(DocumentationNode) -> Unit) = tr {
+        td {
+            val (from, nodes) = entry
+            +"From "
+            a(href = uriProvider.linkTo(from, uri)) { +from.qualifiedName() }
+            table {
+                tbody {
+                    for (node in nodes) {
+                        summaryRow(node)
+                    }
+                }
+            }
+        }
+    }
+
+    private fun FlowContent.renderedSignature(node: DocumentationNode, mode: LanguageService.RenderMode = SUMMARY) {
+        metaMarkup(languageService.render(node, mode))
+    }
+
+    open fun FlowContent.memberDocs(node: DocumentationNode) {
+        div {
+            id = node.signatureForAnchor(logger)
+            h3 { +node.name }
+            pre { renderedSignature(node, FULL) }
+            metaMarkup(node.content)
+            for ((name, sections) in node.content.sections.groupBy { it.tag }) {
+                table {
+                    thead { tr { td { h3 { +name } } } }
+                    tbody {
+                        sections.forEach {
+                            tr {
+                                td { it.subjectName?.let { +it } }
+                                td {
+                                    metaMarkup(it.children)
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    fun appendPackage(node: DocumentationNode) = templateService.composePage(
+            listOf(node),
+            htmlConsumer,
+            headContent = {
+
+            },
+            bodyContent = {
+                h1 { +node.name }
+                metaMarkup(node.content)
+                summaryNodeGroup(node.members(NodeKind.Class), "Classes") { classLikeRow(it) }
+                summaryNodeGroup(node.members(NodeKind.Exception), "Exceptions") { classLikeRow(it) }
+                summaryNodeGroup(node.members(NodeKind.TypeAlias), "Type-aliases") { classLikeRow(it) }
+                summaryNodeGroup(node.members(NodeKind.AnnotationClass), "Annotations") { classLikeRow(it) }
+                summaryNodeGroup(node.members(NodeKind.Enum), "Enums") { classLikeRow(it) }
+
+                summaryNodeGroup(node.members(NodeKind.Function), "Top-level functions summary") { functionLikeSummaryRow(it) }
+                summaryNodeGroup(node.members(NodeKind.Property), "Top-level properties summary") { propertyLikeSummaryRow(it) }
+
+
+                fullDocs(node.members(NodeKind.Function), "Top-level functions") { memberDocs(it) }
+                fullDocs(node.members(NodeKind.Property), "Top-level properties") { memberDocs(it) }
+            }
+    )
+
+    fun FlowContent.qualifiedTypeReference(node: DocumentationNode) {
+        if (node.kind in classLike) {
+            a(href = uriProvider.linkTo(node, uri)) {
+                +node.qualifiedName()
+            }
+            return
+        }
+
+        val targetLink = node.links.single()
+
+        if (targetLink.kind == NodeKind.TypeParameter) {
+            +node.name
+            return
+        }
+
+        val href = if (targetLink.kind == NodeKind.ExternalLink)
+            targetLink.name
+        else
+            uriProvider.linkTo(targetLink, uri)
+
+        a(href = href) {
+            +node.qualifiedNameFromType()
+        }
+        val typeParameters = node.details(NodeKind.Type)
+        if (typeParameters.isNotEmpty()) {
+            +"<"
+            typeParameters.forEach {
+                if (it != typeParameters.first()) {
+                    +", "
+                }
+                qualifiedTypeReference(it)
+            }
+            +">"
+        }
+    }
+
+    open fun FlowContent.classHierarchy(node: DocumentationNode) {
+
+        val superclasses = generateSequence(node.superclass) { it.links.single().superclass }.toList().asReversed() + node
+        table {
+            superclasses.forEach {
+                tr {
+                    if (it != superclasses.first()) {
+                        td {
+                            +"   ↳"
+                        }
+                    }
+                    td {
+                        qualifiedTypeReference(it)
+                    }
+                }
+            }
+        }
+    }
+
+    open fun FlowContent.subclasses(inheritors: List<DocumentationNode>, direct: Boolean) {
+        if (inheritors.isEmpty()) return
+        div {
+            table {
+                thead {
+                    tr {
+                        td {
+                            if (direct)
+                                +"Known Direct Subclasses"
+                            else
+                                +"Known Indirect Subclasses"
+                        }
+                    }
+                }
+                tbody {
+                    inheritors.forEach {
+                        tr {
+                            td {
+                                a(href = uriProvider.linkTo(it, uri)) { +it.classNodeNameWithOuterClass() }
+                            }
+                            td {
+                                metaMarkup(it.summary)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    fun appendClassLike(node: DocumentationNode) = templateService.composePage(
+            listOf(node),
+            htmlConsumer,
+            headContent = {
+
+            },
+            bodyContent = {
+                h1 { +node.name }
+                pre { renderedSignature(node, FULL) }
+                classHierarchy(node)
+
+                val inheritors = generateSequence(node.inheritors) { inheritors ->
+                    inheritors
+                            .flatMap { it.inheritors }
+                            .takeUnless { it.isEmpty() }
+                }
+                subclasses(inheritors.first(), true)
+                subclasses(inheritors.drop(1).flatten().toList(), false)
+
+
+                metaMarkup(node.content)
+
+                h2 { +"Summary" }
+
+                val isCompanion = node.details(NodeKind.Modifier).any { it.name == "companion" }
+                val hasMeaningfulCompanion = !isCompanion && node.companion != null
+
+                fun DocumentationNode.thisTypeExtension() = detail(NodeKind.Receiver).detail(NodeKind.Type).links.any { it == node }
+
+                val functionKind = if (!isCompanion) NodeKind.Function else NodeKind.CompanionObjectFunction
+                val propertyKind = if (!isCompanion) NodeKind.Property else NodeKind.CompanionObjectProperty
+
+                fun DocumentationNode.isFunction() = kind == functionKind
+                fun DocumentationNode.isProperty() = kind == propertyKind
+
+                val functions = node.members(functionKind)
+                val properties = node.members(propertyKind)
+                val inheritedFunctionsByReceiver = node.inheritedMembers(functionKind).groupBy { it.owner!! }
+                val inheritedPropertiesByReceiver = node.inheritedMembers(propertyKind).groupBy { it.owner!! }
+
+
+                val originalExtensions = if (!isCompanion) node.extensions else node.owner!!.extensions
+                val (extensions, inheritedExtensions) = originalExtensions.partition { it.thisTypeExtension() }
+                val extensionFunctions = extensions.filter(DocumentationNode::isFunction).groupBy { it.owner!! }
+                val extensionProperties = extensions.filter(DocumentationNode::isProperty).groupBy { it.owner!! }
+                val inheritedExtensionFunctions = inheritedExtensions.filter(DocumentationNode::isFunction).groupBy { it.owner!! }
+                val inheritedExtensionProperties = inheritedExtensions.filter(DocumentationNode::isProperty).groupBy { it.owner!! }
+
+                val companionFunctions = node.members(NodeKind.CompanionObjectFunction)
+                val companionProperties = node.members(NodeKind.CompanionObjectProperty)
+
+                summaryNodeGroup(node.members.filter { it.kind in NodeKind.classLike }, "Nested classes", headerAsRow = true) { nestedClassSummaryRow(it) }
+
+                summaryNodeGroup(node.members(NodeKind.Constructor), "Constructors", headerAsRow = true) { functionLikeSummaryRow(it) }
+
+                summaryNodeGroup(functions, "Functions", headerAsRow = true) { functionLikeSummaryRow(it) }
+                if (!isCompanion) {
+                    summaryNodeGroup(companionFunctions, "Companion functions", headerAsRow = true) { functionLikeSummaryRow(it) }
+                }
+                summaryNodeGroup(inheritedFunctionsByReceiver.entries, "Inherited functions", headerAsRow = true) { inheritRow(it) { functionLikeSummaryRow(it) } }
+                summaryNodeGroup(extensionFunctions.entries, "Extension functions", headerAsRow = true) { extensionRow(it) { functionLikeSummaryRow(it) } }
+                summaryNodeGroup(inheritedExtensionFunctions.entries, "Inherited extension functions", headerAsRow = true) { extensionRow(it) { functionLikeSummaryRow(it) } }
+
+
+                summaryNodeGroup(properties, "Properties", headerAsRow = true) { propertyLikeSummaryRow(it) }
+                if (!isCompanion) {
+                    summaryNodeGroup(companionProperties, "Companion properties", headerAsRow = true) { propertyLikeSummaryRow(it) }
+                }
+                summaryNodeGroup(inheritedPropertiesByReceiver.entries, "Inherited properties", headerAsRow = true) { inheritRow(it) { propertyLikeSummaryRow(it) } }
+                summaryNodeGroup(extensionProperties.entries, "Extension properties", headerAsRow = true) { extensionRow(it) { propertyLikeSummaryRow(it) } }
+                summaryNodeGroup(inheritedExtensionProperties.entries, "Inherited extension properties", headerAsRow = true) { extensionRow(it) { propertyLikeSummaryRow(it) } }
+
+
+                fullDocs(node.members(NodeKind.Constructor), "Constructors") { memberDocs(it) }
+                fullDocs(functions, "Functions") { memberDocs(it) }
+                fullDocs(properties, "Properties") { memberDocs(it) }
+                if (!isCompanion && !hasMeaningfulCompanion) {
+                    fullDocs(companionFunctions, "Companion functions") { memberDocs(it) }
+                    fullDocs(companionProperties, "Companion properties") { memberDocs(it) }
+                }
+            }
+    )
+
+    fun generateClassesIndex(allTypesNode: DocumentationNode) = templateService.composePage(
+            listOf(allTypesNode),
+            htmlConsumer,
+            headContent = {
+
+            },
+            bodyContent = {
+                h1 { +"Class Index" }
+
+                fun DocumentationNode.classWithNestedClasses(): List<DocumentationNode> =
+                        members.filter { it.kind in classLike }.flatMap(DocumentationNode::classWithNestedClasses) + this
+
+                val classesByFirstLetter = allTypesNode.members
+                        .filterNot { it.kind == NodeKind.ExternalClass }
+                        .flatMap(DocumentationNode::classWithNestedClasses)
+                        .groupBy {
+                            it.classNodeNameWithOuterClass().first().toString()
+                        }
+                        .entries
+                        .sortedBy { (letter) -> letter }
+
+                ul {
+                    classesByFirstLetter.forEach { (letter) ->
+                        li { a(href = "#letter_$letter") { +letter } }
+                    }
+                }
+
+                classesByFirstLetter.forEach { (letter, nodes) ->
+                    h2 {
+                        id = "letter_$letter"
+                        +letter
+                    }
+                    table {
+                        tbody {
+                            for (node in nodes.sortedBy { it.classNodeNameWithOuterClass() }) {
+                                tr {
+                                    td {
+                                        a(href = uriProvider.linkTo(node, uri)) { +node.classNodeNameWithOuterClass() }
+                                    }
+                                    td {
+                                        metaMarkup(node.content)
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+    )
+
+    fun generatePackageIndex(nodes: List<DocumentationNode>) = templateService.composePage(nodes,
+            htmlConsumer,
+            headContent = {
+
+            },
+            bodyContent = {
+                h1 { +"Package Index" }
+                table {
+                    tbody {
+                        for (node in nodes.sortedBy { it.name }) {
+                            tr {
+                                td {
+                                    a(href = uriProvider.linkTo(node, uri)) { +node.name }
+                                }
+                                td {
+                                    metaMarkup(node.content)
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+    )
+
+    private fun FlowContent.fullDocs(
+            nodes: List<DocumentationNode>,
+            header: String,
+            renderNode: FlowContent.(DocumentationNode) -> Unit
+    ) {
+        if (nodes.none()) return
+        h2 {
+            +header
+        }
+        for (node in nodes) {
+            renderNode(node)
+        }
+    }
+}
+
+class ContentToHtmlBuilder(val uriProvider: JavaLayoutHtmlUriProvider, val uri: URI) {
+    fun FlowContent.appendContent(content: List<ContentNode>): Unit = content.forEach { appendContent(it) }
+
+    private fun FlowContent.hN(level: Int, classes: String? = null, block: CommonAttributeGroupFacadeFlowHeadingPhrasingContent.() -> Unit) {
+        when (level) {
+            1 -> h1(classes, block)
+            2 -> h2(classes, block)
+            3 -> h3(classes, block)
+            4 -> h4(classes, block)
+            5 -> h5(classes, block)
+            6 -> h6(classes, block)
+        }
+    }
+
+    fun FlowContent.appendContent(content: ContentNode) {
+        when (content) {
+            is ContentText -> +content.text
+            is ContentSymbol -> span("symbol") { +content.text }
+            is ContentKeyword -> span("keyword") { +content.text }
+            is ContentIdentifier -> span("identifier") {
+                content.signature?.let { id = it }
+                +content.text
+            }
+
+            is ContentHeading -> hN(level = content.level) { appendContent(content.children) }
+
+            is ContentEntity -> +content.text
+
+            is ContentStrong -> strong { appendContent(content.children) }
+            is ContentStrikethrough -> del { appendContent(content.children) }
+            is ContentEmphasis -> em { appendContent(content.children) }
+
+            is ContentOrderedList -> ol { appendContent(content.children) }
+            is ContentUnorderedList -> ul { appendContent(content.children) }
+            is ContentListItem -> consumer.li {
+                (content.children.singleOrNull() as? ContentParagraph)
+                        ?.let { paragraph -> appendContent(paragraph.children) }
+                        ?: appendContent(content.children)
+            }
+
+
+            is ContentCode -> pre { code { appendContent(content.children) } }
+            is ContentBlockSampleCode -> pre { code {} }
+            is ContentBlockCode -> pre { code {} }
+
+
+            is ContentNonBreakingSpace -> +nbsp
+            is ContentSoftLineBreak, is ContentIndentedSoftLineBreak -> {
+            }
+
+            is ContentParagraph -> p { appendContent(content.children) }
+
+            is ContentNodeLink -> {
+                a(href = content.node?.let { uriProvider.linkTo(it, uri) }
+                        ?: "#unresolved") { appendContent(content.children) }
+            }
+            is ContentExternalLink -> {
+                a(href = content.href) { appendContent(content.children) }
+            }
+
+            is ContentBlock -> appendContent(content.children)
+        }
+    }
+}
+
+class JavaLayoutHtmlFormatOutputBuilderFactoryImpl @Inject constructor(
+        val uriProvider: JavaLayoutHtmlUriProvider,
+        val languageService: LanguageService,
+        val templateService: JavaLayoutHtmlTemplateService,
+        val logger: DokkaLogger
+) : JavaLayoutHtmlFormatOutputBuilderFactory {
+    override fun createOutputBuilder(output: Appendable, node: DocumentationNode): JavaLayoutHtmlFormatOutputBuilder {
+        return createOutputBuilder(output, uriProvider.mainUri(node))
+    }
+
+    override fun createOutputBuilder(output: Appendable, uri: URI): JavaLayoutHtmlFormatOutputBuilder {
+        return JavaLayoutHtmlFormatOutputBuilder(output, languageService, uriProvider, templateService, logger, uri)
+    }
+}
\ 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..63668e7
--- /dev/null
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlGenerator.kt
@@ -0,0 +1,145 @@
+package org.jetbrains.dokka.Formats
+
+import com.google.inject.Inject
+import com.google.inject.name.Named
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.NodeKind.Companion.classLike
+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 packageListService: PackageListService,
+        val outputBuilderFactoryService: JavaLayoutHtmlFormatOutputBuilderFactory,
+        val logger: DokkaLogger
+) : Generator, JavaLayoutHtmlUriProvider {
+
+    @set:Inject(optional = true)
+    var outlineFactoryService: JavaLayoutHtmlFormatOutlineFactoryService? = null
+
+    fun createOutputBuilderForNode(node: DocumentationNode, output: Appendable)
+            = outputBuilderFactoryService.createOutputBuilder(output, 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
+                if (owner!!.kind in classLike &&
+                        (node.kind == NodeKind.CompanionObjectProperty || node.kind == NodeKind.CompanionObjectFunction) &&
+                        owner.companion != null
+                ) {
+                    val signature = node.detail(NodeKind.Signature)
+                    val originalFunction = owner.companion!!.members.first { it.detailOrNull(NodeKind.Signature)?.name == signature.name }
+                    tryGetMainUri(owner.companion!!)?.resolveInPage(originalFunction)
+                } else {
+                    tryGetMainUri(owner)?.resolveInPage(node)
+                }
+            }
+            NodeKind.TypeParameter, NodeKind.Parameter -> node.path.asReversed().drop(1).firstNotNullResult(this::tryGetMainUri)?.resolveInPage(node)
+            NodeKind.AllTypes -> 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 {
+            outputBuilderFactoryService.createOutputBuilder(it, 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>) {
+        nodes.filter { it.kind == NodeKind.Module }.forEach { module ->
+            val moduleRoot = root.resolve(module.name)
+            val packageListFile = moduleRoot.resolve("package-list")
+            packageListFile.writeText(packageListService.formatPackageList(module as DocumentationModule))
+        }
+    }
+}
+
+interface JavaLayoutHtmlFormatOutputBuilderFactory {
+    fun createOutputBuilder(output: Appendable, uri: URI): JavaLayoutHtmlFormatOutputBuilder
+    fun createOutputBuilder(output: Appendable, node: DocumentationNode): JavaLayoutHtmlFormatOutputBuilder
+}
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt
new file mode 100644
index 0000000..1a0763f
--- /dev/null
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt
@@ -0,0 +1,28 @@
+package org.jetbrains.dokka.Formats
+
+import org.jetbrains.dokka.DocumentationModule
+import org.jetbrains.dokka.ExternalDocumentationLinkResolver.Companion.DOKKA_PARAM_PREFIX
+import org.jetbrains.dokka.NodeKind
+import org.jetbrains.dokka.PackageListService
+
+class JavaLayoutHtmlPackageListService : PackageListService {
+
+    private fun StringBuilder.appendParam(name: String, value: String) {
+        append(DOKKA_PARAM_PREFIX)
+        append(name)
+        append(":")
+        appendln(value)
+    }
+
+    override fun formatPackageList(module: DocumentationModule): String {
+        val packages = module.members(NodeKind.Package).map { it.name }
+
+        return buildString {
+            appendParam("format", "java-layout-html")
+            for (p in packages) {
+                appendln(p)
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt b/core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt
deleted file mode 100644
index 7d08238..0000000
--- a/core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt
+++ /dev/null
@@ -1,662 +0,0 @@
-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
-import org.jetbrains.dokka.*
-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,
-        val uriProvider: JavaLayoutHtmlUriProvider,
-        val templateService: JavaLayoutHtmlTemplateService,
-        val logger: DokkaLogger,
-        val uri: URI
-) {
-
-    val htmlConsumer = output.appendHTML()
-
-    val contentToHtmlBuilder = ContentToHtmlBuilder(uriProvider, uri)
-
-    private fun <T> FlowContent.summaryNodeGroup(nodes: Iterable<T>, header: String, headerAsRow: Boolean = false, row: TBODY.(T) -> Unit) {
-        if (nodes.none()) return
-        if (!headerAsRow) {
-            h2 { +header }
-        }
-        table {
-            if (headerAsRow) thead { tr { td { h3 { +header } } } }
-            tbody {
-                nodes.forEach { node ->
-                    row(node)
-                }
-            }
-        }
-    }
-
-    fun FlowContent.metaMarkup(content: ContentNode) = with(contentToHtmlBuilder) {
-        appendContent(content)
-    }
-
-    fun FlowContent.metaMarkup(content: List<ContentNode>) = with(contentToHtmlBuilder) {
-        appendContent(content)
-    }
-
-    private fun TBODY.formatClassLikeRow(node: DocumentationNode) = tr {
-        td { a(href = uriProvider.linkTo(node, uri)) { +node.simpleName() } }
-        td { metaMarkup(node.summary) }
-    }
-
-    private fun FlowContent.modifiers(node: DocumentationNode) {
-        for (modifier in node.details(NodeKind.Modifier)) {
-            renderedSignature(modifier, SUMMARY)
-        }
-    }
-
-    private fun FlowContent.shortFunctionParametersList(func: DocumentationNode) {
-        val params = func.details(NodeKind.Parameter)
-                .map { languageService.render(it, FULL) }
-                .run {
-                    drop(1).fold(listOfNotNull(firstOrNull())) { acc, node ->
-                        acc + ContentText(", ") + node
-                    }
-                }
-        metaMarkup(listOf(ContentText("(")) + params + listOf(ContentText(")")))
-    }
-
-
-    private fun TBODY.functionLikeSummaryRow(node: DocumentationNode) = tr {
-        if (node.kind != NodeKind.Constructor) {
-            td {
-                modifiers(node)
-                renderedSignature(node.detail(NodeKind.Type), SUMMARY)
-            }
-        }
-        td {
-            div {
-                code {
-                    a(href = uriProvider.linkTo(node, uri)) { +node.name }
-                    shortFunctionParametersList(node)
-                }
-            }
-
-            metaMarkup(node.summary)
-        }
-    }
-
-    private fun TBODY.propertyLikeSummaryRow(node: DocumentationNode) = tr {
-        td {
-            modifiers(node)
-            renderedSignature(node.detail(NodeKind.Type), SUMMARY)
-        }
-        td {
-            div {
-                code {
-                    a(href = uriProvider.linkTo(node, uri)) { +node.name }
-                }
-            }
-
-            metaMarkup(node.summary)
-        }
-    }
-
-    private fun TBODY.nestedClassSummaryRow(node: DocumentationNode) = tr {
-        td {
-            modifiers(node)
-        }
-        td {
-            div {
-                code {
-                    a(href = uriProvider.linkTo(node, uri)) { +node.name }
-                }
-            }
-
-            metaMarkup(node.summary)
-        }
-    }
-
-    private fun TBODY.inheritRow(entry: Map.Entry<DocumentationNode, List<DocumentationNode>>, summaryRow: TBODY.(DocumentationNode) -> Unit) = tr {
-        td {
-            val (from, nodes) = entry
-            +"From class "
-            a(href = uriProvider.linkTo(from.owner!!, uri)) { +from.qualifiedName() }
-            table {
-                tbody {
-                    for (node in nodes) {
-                        summaryRow(node)
-                    }
-                }
-            }
-        }
-    }
-
-    private fun FlowContent.renderedSignature(node: DocumentationNode, mode: LanguageService.RenderMode = SUMMARY) {
-        metaMarkup(languageService.render(node, mode))
-    }
-
-    private fun FlowContent.fullFunctionDocs(node: DocumentationNode) {
-        div {
-            id = node.signatureForAnchor(logger)
-            h3 { +node.name }
-            pre { renderedSignature(node, FULL) }
-            metaMarkup(node.content)
-            for ((name, sections) in node.content.sections.groupBy { it.tag }) {
-                table {
-                    thead { tr { td { h3 { +name } } } }
-                    tbody {
-                        sections.forEach {
-                            tr {
-                                td { it.subjectName?.let { +it } }
-                                td {
-                                    metaMarkup(it.children)
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    private fun FlowContent.fullPropertyDocs(node: DocumentationNode) {
-        fullFunctionDocs(node)
-    }
-
-    fun appendPackage(node: DocumentationNode) = templateService.composePage(
-            listOf(node),
-            htmlConsumer,
-            headContent = {
-                title(node.nameWithOuterClass())
-            },
-            bodyContent = {
-                h1 { +node.name }
-                metaMarkup(node.content)
-                summaryNodeGroup(node.members(NodeKind.Class), "Classes") { formatClassLikeRow(it) }
-                summaryNodeGroup(node.members(NodeKind.Exception), "Exceptions") { formatClassLikeRow(it) }
-                summaryNodeGroup(node.members(NodeKind.TypeAlias), "Type-aliases") { formatClassLikeRow(it) }
-                summaryNodeGroup(node.members(NodeKind.AnnotationClass), "Annotations") { formatClassLikeRow(it) }
-                summaryNodeGroup(node.members(NodeKind.Enum), "Enums") { formatClassLikeRow(it) }
-
-                summaryNodeGroup(node.members(NodeKind.Function), "Top-level functions summary") { functionLikeSummaryRow(it) }
-                summaryNodeGroup(node.members(NodeKind.Property), "Top-level properties summary") { propertyLikeSummaryRow(it) }
-
-
-                fullDocs(node.members(NodeKind.Function), { h2 { +"Top-level functions" } }) { fullFunctionDocs(it) }
-                fullDocs(node.members(NodeKind.Property), { h2 { +"Top-level properties" } }) { fullPropertyDocs(it) }
-            }
-    )
-
-    fun FlowContent.classHierarchy(node: DocumentationNode) {
-
-        val superclasses = generateSequence(node) { it.superclass }.toList().asReversed()
-        table {
-            superclasses.forEach {
-                tr {
-                    if (it != superclasses.first()) {
-                        td {
-                            +"   ↳"
-                        }
-                    }
-                    td {
-                        a(href = uriProvider.linkTo(it, uri)) { +it.qualifiedName() }
-                    }
-                }
-            }
-        }
-    }
-
-    fun appendClassLike(node: DocumentationNode) = templateService.composePage(
-            listOf(node),
-            htmlConsumer,
-            headContent = {
-                title(node.nameWithOuterClass())
-            },
-            bodyContent = {
-                h1 { +node.name }
-                pre { renderedSignature(node, FULL) }
-                classHierarchy(node)
-
-                metaMarkup(node.content)
-
-                h2 { +"Summary" }
-
-                fun DocumentationNode.isFunction() = kind == NodeKind.Function || kind == NodeKind.CompanionObjectFunction
-                fun DocumentationNode.isProperty() = kind == NodeKind.Property || kind == NodeKind.CompanionObjectProperty
-
-                val functionsToDisplay = node.members.filter(DocumentationNode::isFunction)
-                val properties = node.members.filter(DocumentationNode::isProperty)
-                val inheritedFunctionsByReceiver = node.inheritedMembers.filter(DocumentationNode::isFunction).groupBy { it.owner!! }
-                val inheritedPropertiesByReceiver = node.inheritedMembers.filter(DocumentationNode::isProperty).groupBy { it.owner!! }
-                val extensionProperties = node.extensions.filter(DocumentationNode::isProperty)
-                val extensionFunctions = node.extensions.filter(DocumentationNode::isFunction)
-
-                summaryNodeGroup(node.members.filter { it.kind in NodeKind.classLike }, "Nested classes", headerAsRow = true) { nestedClassSummaryRow(it) }
-
-                summaryNodeGroup(node.members(NodeKind.Constructor), "Constructors", headerAsRow = true) { functionLikeSummaryRow(it) }
-
-                summaryNodeGroup(functionsToDisplay, "Functions", headerAsRow = true) { functionLikeSummaryRow(it) }
-                summaryNodeGroup(inheritedFunctionsByReceiver.entries, "Inherited functions", headerAsRow = true) { inheritRow(it) { functionLikeSummaryRow(it) } }
-                summaryNodeGroup(extensionFunctions, "Extension functions", headerAsRow = true) { functionLikeSummaryRow(it) }
-
-
-                summaryNodeGroup(properties, "Properties", headerAsRow = true) { propertyLikeSummaryRow(it) }
-                summaryNodeGroup(inheritedPropertiesByReceiver.entries, "Inherited properties", headerAsRow = true) { inheritRow(it) { propertyLikeSummaryRow(it) } }
-                summaryNodeGroup(extensionProperties, "Extension properties", headerAsRow = true) { propertyLikeSummaryRow(it) }
-
-                fullDocs(node.members(NodeKind.Constructor), { h2 { +"Constructors" } }) { fullFunctionDocs(it) }
-                fullDocs(functionsToDisplay, { h2 { +"Functions" } }) { fullFunctionDocs(it) }
-                fullDocs(extensionFunctions, { h2 { +"Extension functions" } }) { fullFunctionDocs(it) }
-                fullDocs(properties, { h2 { +"Properties" } }) { fullPropertyDocs(it) }
-                fullDocs(extensionProperties, { h2 { +"Extension properties" } }) { fullPropertyDocs(it) }
-            }
-    )
-
-    fun generateClassesIndex(allTypesNode: DocumentationNode) = templateService.composePage(
-            listOf(allTypesNode),
-            htmlConsumer,
-            headContent = {
-
-            },
-            bodyContent = {
-                h1 { +"Class Index" }
-
-                fun DocumentationNode.classWithNestedClasses(): List<DocumentationNode> =
-                        members.filter { it.kind in classLike }.flatMap(DocumentationNode::classWithNestedClasses) + this
-
-                val classesByFirstLetter = allTypesNode.members
-                        .filterNot { it.kind == NodeKind.ExternalClass }
-                        .flatMap(DocumentationNode::classWithNestedClasses)
-                        .groupBy {
-                            it.classNodeNameWithOuterClass().first().toString()
-                        }
-                        .entries
-                        .sortedBy { (letter) -> letter }
-
-                ul {
-                    classesByFirstLetter.forEach { (letter) ->
-                        li { a(href = "#letter_$letter") { +letter } }
-                    }
-                }
-
-                classesByFirstLetter.forEach { (letter, nodes) ->
-                    h2 {
-                        id = "letter_$letter"
-                        +letter
-                    }
-                    table {
-                        tbody {
-                            for (node in nodes.sortedBy { it.classNodeNameWithOuterClass() }) {
-                                tr {
-                                    td {
-                                        a(href = uriProvider.linkTo(node, uri)) { +node.classNodeNameWithOuterClass() }
-                                    }
-                                    td {
-                                        metaMarkup(node.content)
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-    )
-
-    fun generatePackageIndex(nodes: List<DocumentationNode>) = templateService.composePage(nodes,
-            htmlConsumer,
-            headContent = {
-
-            },
-            bodyContent = {
-                h1 { +"Package Index" }
-                table {
-                    tbody {
-                        for (node in nodes.sortedBy { it.name }) {
-                            tr {
-                                td {
-                                    a(href = uriProvider.linkTo(node, uri)) { +node.name }
-                                }
-                                td {
-                                    metaMarkup(node.content)
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-    )
-
-    private fun FlowContent.fullDocs(
-            nodes: List<DocumentationNode>,
-            header: FlowContent.() -> Unit,
-            renderNode: FlowContent.(DocumentationNode) -> Unit
-    ) {
-        if (nodes.none()) return
-        header()
-        for (node in nodes) {
-            renderNode(node)
-        }
-    }
-}
-
-class ContentToHtmlBuilder(val uriProvider: JavaLayoutHtmlUriProvider, val uri: URI) {
-    fun FlowContent.appendContent(content: List<ContentNode>): Unit = content.forEach { appendContent(it) }
-
-    private fun FlowContent.hN(level: Int, classes: String? = null, block: CommonAttributeGroupFacadeFlowHeadingPhrasingContent.() -> Unit) {
-        when (level) {
-            1 -> h1(classes, block)
-            2 -> h2(classes, block)
-            3 -> h3(classes, block)
-            4 -> h4(classes, block)
-            5 -> h5(classes, block)
-            6 -> h6(classes, block)
-        }
-    }
-
-    fun FlowContent.appendContent(content: ContentNode) {
-        when (content) {
-            is ContentText -> +content.text
-            is ContentSymbol -> span("symbol") { +content.text }
-            is ContentKeyword -> span("keyword") { +content.text }
-            is ContentIdentifier -> span("identifier") {
-                content.signature?.let { id = it }
-                +content.text
-            }
-
-            is ContentHeading -> hN(level = content.level) { appendContent(content.children) }
-
-            is ContentEntity -> +content.text
-
-            is ContentStrong -> strong { appendContent(content.children) }
-            is ContentStrikethrough -> del { appendContent(content.children) }
-            is ContentEmphasis -> em { appendContent(content.children) }
-
-            is ContentOrderedList -> ol { appendContent(content.children) }
-            is ContentUnorderedList -> ul { appendContent(content.children) }
-            is ContentListItem -> consumer.li {
-                (content.children.singleOrNull() as? ContentParagraph)
-                        ?.let { paragraph -> appendContent(paragraph.children) }
-                        ?: appendContent(content.children)
-            }
-
-
-            is ContentCode -> pre { code { appendContent(content.children) } }
-            is ContentBlockSampleCode -> pre { code {} }
-            is ContentBlockCode -> pre { code {} }
-
-
-            is ContentNonBreakingSpace -> +nbsp
-            is ContentSoftLineBreak, is ContentIndentedSoftLineBreak -> {
-            }
-
-            is ContentParagraph -> p { appendContent(content.children) }
-
-            is ContentNodeLink -> {
-                a(href = content.node?.let { uriProvider.linkTo(it, uri) } ?: "#unresolved") { appendContent(content.children) }
-            }
-            is ContentExternalLink -> {
-                a(href = content.href) { appendContent(content.children) }
-            }
-
-            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)
-        }
-    })
-}
diff --git a/core/src/main/kotlin/Model/DocumentationNode.kt b/core/src/main/kotlin/Model/DocumentationNode.kt
index 4b10879..a145ae8 100644
--- a/core/src/main/kotlin/Model/DocumentationNode.kt
+++ b/core/src/main/kotlin/Model/DocumentationNode.kt
@@ -106,10 +106,10 @@
         get() = references(RefKind.Platform).map { it.to.name }
 
     val supertypes: List<DocumentationNode>
-        get() = references(RefKind.Superclass).map { it.to }
+        get() = details(NodeKind.Supertype)
 
     val superclass: DocumentationNode?
-        get() = supertypes.find { it.kind == NodeKind.Class }
+        get() = supertypes.firstOrNull { it.links.any { it.kind in NodeKind.classLike } }
 
     // TODO: Should we allow node mutation? Model merge will copy by ref, so references are transparent, which could nice
     fun addReferenceTo(to: DocumentationNode, kind: RefKind) {
@@ -162,7 +162,7 @@
     }
 
 fun DocumentationNode.findOrCreatePackageNode(packageName: String, packageContent: Map<String, Content>, refGraph: NodeReferenceGraph): DocumentationNode {
-    val existingNode = members(NodeKind.Package).firstOrNull { it.name  == packageName }
+    val existingNode = members(NodeKind.Package).firstOrNull { it.name == packageName }
     if (existingNode != null) {
         return existingNode
     }
@@ -180,7 +180,8 @@
         RefKind.Detail -> child.addReferenceTo(this, RefKind.Owner)
         RefKind.Member -> child.addReferenceTo(this, RefKind.Owner)
         RefKind.Owner -> child.addReferenceTo(this, RefKind.Member)
-        else -> { /* Do not add any links back for other types */ }
+        else -> { /* Do not add any links back for other types */
+        }
     }
 }
 
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