Merge remote-tracking branch 'upstream/java-layout-html-format' into devsite-with-java-layout-html
diff --git a/core/src/main/kotlin/Formats/FormatDescriptor.kt b/core/src/main/kotlin/Formats/FormatDescriptor.kt
index aa00df9..a1120c0 100644
--- a/core/src/main/kotlin/Formats/FormatDescriptor.kt
+++ b/core/src/main/kotlin/Formats/FormatDescriptor.kt
@@ -3,6 +3,7 @@
 import com.google.inject.Binder
 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 kotlin.reflect.KClass
@@ -23,11 +24,12 @@
 
     override fun configureOutput(binder: Binder): Unit = with(binder) {
         bind<Generator>() toType NodeLocationAwareGenerator::class
-
-        bind<OutlineFormatService>() toOptional (outlineServiceClass)
-        bind<FormatService>() toOptional formatServiceClass
         bind<NodeLocationAwareGenerator>() toType generatorServiceClass
-        bind<PackageListService>() toOptional packageListServiceClass
+
+
+        lazyBind<OutlineFormatService>() toOptional (outlineServiceClass)
+        lazyBind<FormatService>() toOptional formatServiceClass
+        lazyBind<PackageListService>() toOptional packageListServiceClass
     }
 
     abstract val formatServiceClass: KClass<out FormatService>?
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt b/core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt
index 58a5b99..e1951ea 100644
--- a/core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtmlFormat.kt
@@ -12,9 +12,11 @@
 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
@@ -29,7 +31,7 @@
         bind<LanguageService>() toType languageServiceClass
         bind<JavaLayoutHtmlTemplateService>() toType templateServiceClass
         bind<JavaLayoutHtmlUriProvider>() toType generatorServiceClass
-        bind<JavaLayoutHtmlFormatOutlineFactoryService>() toOptional outlineFactoryClass
+        lazyBind<JavaLayoutHtmlFormatOutlineFactoryService>() toOptional outlineFactoryClass
     }
 
     val generatorServiceClass = JavaLayoutHtmlFormatGenerator::class
@@ -61,7 +63,10 @@
                 bodyContent: BODY.() -> Unit
         ) {
             tagConsumer.html {
-                head(headContent)
+                head {
+                    meta(charset = "UTF-8")
+                    headContent()
+                }
                 body(block = bodyContent)
             }
         }
@@ -77,6 +82,7 @@
         val languageService: LanguageService,
         val uriProvider: JavaLayoutHtmlUriProvider,
         val templateService: JavaLayoutHtmlTemplateService,
+        val logger: DokkaLogger,
         val uri: URI
 ) {
 
@@ -84,11 +90,10 @@
 
     val contentToHtmlBuilder = ContentToHtmlBuilder(uriProvider, uri)
 
-    private fun FlowContent.summaryNodeGroup(nodes: Iterable<DocumentationNode>, header: String, headerAsRow: Boolean = false, row: TBODY.(DocumentationNode) -> Unit) {
+    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 }
-            hr()
         }
         table {
             if (headerAsRow) thead { tr { td { h3 { +header } } } }
@@ -113,7 +118,7 @@
         td { metaMarkup(node.summary) }
     }
 
-    private fun TBODY.formatFunctionSummaryRow(node: DocumentationNode) = tr {
+    private fun TBODY.functionSummaryRow(node: DocumentationNode) = tr {
         td {
             for (modifier in node.details(NodeKind.Modifier)) {
                 renderedSignature(modifier, SUMMARY)
@@ -129,12 +134,28 @@
         }
     }
 
+    private fun TBODY.formatInheritRow(entry: Map.Entry<DocumentationNode, List<DocumentationNode>>) = tr {
+        td {
+            val (from, nodes) = entry
+            +"From class "
+            a(href = uriProvider.linkTo(from.owner!!, uri)) { +from.qualifiedName() }
+            table {
+                tbody {
+                    for (node in nodes) {
+                        functionSummaryRow(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)
@@ -154,7 +175,10 @@
                 }
             }
         }
-        a { id = node.signature() }
+    }
+
+    private fun FlowContent.fullPropertyDocs(node: DocumentationNode) {
+        fullFunctionDocs(node)
     }
 
     fun appendPackage(node: DocumentationNode) = templateService.composePage(
@@ -172,17 +196,33 @@
                 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") { formatFunctionSummaryRow(it) }
+                summaryNodeGroup(node.members(NodeKind.Function), "Top-level functions summary") { functionSummaryRow(it) }
+                summaryNodeGroup(node.members(NodeKind.Property), "Top-level properties summary") { functionSummaryRow(it) }
 
 
-                h2 { +"Top-level functions" }
-                hr()
-                for (function in node.members(NodeKind.Function)) {
-                    fullFunctionDocs(function)
-                }
+                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),
@@ -193,22 +233,116 @@
             bodyContent = {
                 h1 { +node.name }
                 pre { renderedSignature(node, FULL) }
+                classHierarchy(node)
+
                 metaMarkup(node.content)
 
                 h2 { +"Summary" }
-                hr()
 
-                val functionsToDisplay = node.members(NodeKind.Function) + node.members(NodeKind.CompanionObjectFunction)
+                fun DocumentationNode.isFunction() = kind == NodeKind.Function || kind == NodeKind.CompanionObjectFunction
+                fun DocumentationNode.isProperty() = kind == NodeKind.Property || kind == NodeKind.CompanionObjectProperty
 
-                summaryNodeGroup(functionsToDisplay, "Functions", headerAsRow = true) { formatFunctionSummaryRow(it) }
+                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)
 
-                h2 { +"Functions" }
-                hr()
-                for (function in functionsToDisplay) {
-                    fullFunctionDocs(function)
+
+                summaryNodeGroup(functionsToDisplay, "Functions", headerAsRow = true) { functionSummaryRow(it) }
+                summaryNodeGroup(inheritedFunctionsByReceiver.entries, "Inherited functions", headerAsRow = true) { formatInheritRow(it) }
+                summaryNodeGroup(extensionFunctions, "Extension functions", headerAsRow = true) { functionSummaryRow(it) }
+
+
+                summaryNodeGroup(properties, "Properties", headerAsRow = true) { functionSummaryRow(it) }
+                summaryNodeGroup(inheritedPropertiesByReceiver.entries, "Inherited properties", headerAsRow = true) { formatInheritRow(it) }
+                summaryNodeGroup(extensionProperties, "Extension properties", headerAsRow = true) { functionSummaryRow(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" }
+                val classesByFirstLetter = allTypesNode.members.groupBy {
+                    it.name.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) {
+                                tr {
+                                    td {
+                                        a(href = uriProvider.linkTo(node, uri)) { +node.name }
+                                    }
+                                    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) {
@@ -295,11 +429,14 @@
         @Named("outputDir") val root: File,
         val languageService: LanguageService,
         val templateService: JavaLayoutHtmlTemplateService,
-        val outlineFactoryService: JavaLayoutHtmlFormatOutlineFactoryService
+        val logger: DokkaLogger
 ) : Generator, JavaLayoutHtmlUriProvider {
 
+    @set:Inject(optional = true)
+    var outlineFactoryService: JavaLayoutHtmlFormatOutlineFactoryService? = null
+
     fun createOutputBuilderForNode(node: DocumentationNode, output: Appendable)
-            = JavaLayoutHtmlFormatOutputBuilder(output, languageService, this, templateService, mainUri(node))
+            = JavaLayoutHtmlFormatOutputBuilder(output, languageService, this, templateService, logger, mainUri(node))
 
     override fun tryGetContainerUri(node: DocumentationNode): URI? {
         return when (node.kind) {
@@ -314,12 +451,15 @@
         return when (node.kind) {
             NodeKind.Package -> tryGetContainerUri(node)?.resolve("package-summary.html")
             in classLike -> tryGetContainerUri(node)?.resolve("#")
-            in memberLike -> tryGetMainUri(node.owner!!)?.resolve("#${node.signatureUrlEncoded()}")
-            NodeKind.AllTypes -> tryGetContainerUri(node.owner!!)?.resolve("allclasses.html")
+            in memberLike -> tryGetMainUri(node.owner!!)?.resolveInPage(node)
+            NodeKind.TypeParameter -> 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.simpleName() + ".html")
         fileForClass.bufferedWriter().use {
@@ -342,12 +482,30 @@
         }
     }
 
+    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)
-        module.members.filter { it.kind == NodeKind.Package }.forEach { buildPackage(it, moduleRoot) }
+        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>) {
@@ -362,7 +520,7 @@
             return writer
         }
 
-        outlineFactoryService.generateOutlines(::provideOutput, nodes)
+        outlineFactoryService?.generateOutlines(::provideOutput, nodes)
 
         uriToWriter.values.forEach { it.close() }
     }
@@ -374,44 +532,57 @@
     }
 }
 
-fun DocumentationNode.signature() = detail(NodeKind.Signature).name
-fun DocumentationNode.signatureUrlEncoded() = URLEncoder.encode(detail(NodeKind.Signature).name, "UTF-8")
-
-
-fun URI.relativeTo(base: URI): URI {
-    var base = base
-    var child = this
-    // Normalize paths to remove . and .. segments
-    base = base.normalize()
-    child = child.normalize()
-
-    // 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)
-    }
-
-    // Remove common prefix segments
-    var i = 0
-    while (i < bParts.size && i < cParts.size && bParts[i] == cParts[i]) {
-        i++
-    }
-
-
-    // Construct the relative path
-    val sb = StringBuilder()
-    for (j in 0 until bParts.size - i) {
-        sb.append("../")
-    }
-    for (j in i until cParts.size) {
-        if (j != i) {
-            sb.append("/")
+fun DocumentationNode.signatureForAnchor(logger: DokkaLogger): String = when (kind) {
+    NodeKind.Function -> buildString {
+        detailOrNull(NodeKind.Receiver)?.let {
+            append("(")
+            append(it.detail(NodeKind.Type).qualifiedNameFromType())
+            append(").")
         }
-        sb.append(cParts[j])
+        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 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(sb.toString())
+    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/Kotlin/DocumentationBuilder.kt b/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt
index 61bf50d..dd29cfc 100644
--- a/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt
+++ b/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt
@@ -232,6 +232,7 @@
             val externalLink = linkResolver.externalDocumentationLinkResolver.buildExternalDocumentationLink(classifierDescriptor)
             if (externalLink != null) {
                 node.append(DocumentationNode(externalLink, Content.Empty, NodeKind.ExternalLink), RefKind.Link)
+                node.append(DocumentationNode(classifierDescriptor.fqNameUnsafe.asString(), Content.Empty, NodeKind.QualifiedName), RefKind.Detail)
             } else {
                 link(node, classifierDescriptor,
                         if (classifierDescriptor.isBoringBuiltinClass()) RefKind.HiddenLink else RefKind.Link)
diff --git a/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt b/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt
index f33c8c9..6f8ff44 100644
--- a/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt
+++ b/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt
@@ -47,8 +47,7 @@
             val typeParameter = functionWithTypeParameter.details(NodeKind.TypeParameter).first()
             if (functionWithTypeParameter.kind == NodeKind.Function) {
                 renderFunction(functionWithTypeParameter, RenderMode.SUMMARY, SummarizingMapper(receiverKind, typeParameter.name))
-            }
-            else {
+            } else {
                 renderProperty(functionWithTypeParameter, RenderMode.SUMMARY, SummarizingMapper(receiverKind, typeParameter.name))
             }
         }
@@ -102,7 +101,7 @@
         fun renderReceiver(receiver: DocumentationNode, to: ContentBlock)
     }
 
-    private class SummarizingMapper(val kind: ReceiverKind, val typeParameterName: String): SignatureMapper {
+    private class SummarizingMapper(val kind: ReceiverKind, val typeParameterName: String) : SignatureMapper {
         override fun renderReceiver(receiver: DocumentationNode, to: ContentBlock) {
             to.append(ContentIdentifier(kind.receiverName, IdentifierKind.SummarizedTypeName))
             to.text("<$typeParameterName>")
@@ -116,7 +115,7 @@
     }
 
     private fun <T> ContentBlock.renderList(nodes: List<T>, separator: String = ", ",
-                                        noWrap: Boolean = false, renderItem: (T) -> Unit) {
+                                            noWrap: Boolean = false, renderItem: (T) -> Unit) {
         if (nodes.none())
             return
         renderItem(nodes.first())
@@ -131,7 +130,7 @@
         }
     }
 
-    private fun ContentBlock.renderLinked(node: DocumentationNode, body: ContentBlock.(DocumentationNode)->Unit) {
+    private fun ContentBlock.renderLinked(node: DocumentationNode, body: ContentBlock.(DocumentationNode) -> Unit) {
         val to = node.links.firstOrNull()
         if (to == null)
             body(node)
@@ -215,13 +214,13 @@
 
     private fun ContentBlock.renderModifier(node: DocumentationNode, nowrap: Boolean = false) {
         when (node.name) {
-            "final", "public", "var" -> {}
+            "final", "public", "var" -> {
+            }
             else -> {
                 keyword(node.name)
                 if (nowrap) {
                     nbsp()
-                }
-                else {
+                } else {
                     text(" ")
                 }
             }
@@ -238,11 +237,12 @@
             nbsp()
             symbol(":")
             nbsp()
-            renderList(constraints, noWrap=true) {
+            renderList(constraints, noWrap = true) {
                 renderType(it, renderMode)
             }
         }
     }
+
     private fun ContentBlock.renderParameter(node: DocumentationNode, renderMode: RenderMode) {
         if (renderMode == RenderMode.FULL) {
             renderAnnotationsForNode(node)
@@ -401,8 +401,7 @@
             symbol(")")
             symbol(": ")
             renderType(node.detail(NodeKind.Type), renderMode)
-        }
-        else {
+        } else {
             symbol(")")
         }
         renderExtraTypeParameterConstraints(node, renderMode)
@@ -428,7 +427,7 @@
         }
     }
 
-    private fun needReturnType(node: DocumentationNode) = when(node.kind) {
+    private fun needReturnType(node: DocumentationNode) = when (node.kind) {
         NodeKind.Constructor -> false
         else -> !node.isUnitReturnType()
     }
@@ -475,4 +474,7 @@
     }
 }
 
-fun DocumentationNode.qualifiedNameFromType() = (links.firstOrNull() ?: hiddenLinks.firstOrNull())?.qualifiedName() ?: name
+fun DocumentationNode.qualifiedNameFromType() =
+        details.firstOrNull { it.kind == NodeKind.QualifiedName }?.name
+                ?: (links.firstOrNull() ?: hiddenLinks.firstOrNull())?.qualifiedName()
+                ?: name
diff --git a/core/src/main/kotlin/Model/DocumentationNode.kt b/core/src/main/kotlin/Model/DocumentationNode.kt
index da85cab..4b10879 100644
--- a/core/src/main/kotlin/Model/DocumentationNode.kt
+++ b/core/src/main/kotlin/Model/DocumentationNode.kt
@@ -48,6 +48,7 @@
     Signature,
 
     ExternalLink,
+    QualifiedName,
     Platform,
 
     AllTypes,
@@ -104,6 +105,12 @@
     val platforms: List<String>
         get() = references(RefKind.Platform).map { it.to.name }
 
+    val supertypes: List<DocumentationNode>
+        get() = references(RefKind.Superclass).map { it.to }
+
+    val superclass: DocumentationNode?
+        get() = supertypes.find { it.kind == NodeKind.Class }
+
     // 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) {
         references.add(DocumentationReference(this, to, kind))
diff --git a/core/src/main/kotlin/Utilities/DokkaModules.kt b/core/src/main/kotlin/Utilities/DokkaModules.kt
index 6b5e153..763e29a 100644
--- a/core/src/main/kotlin/Utilities/DokkaModules.kt
+++ b/core/src/main/kotlin/Utilities/DokkaModules.kt
@@ -73,7 +73,9 @@
 
 inline fun <reified T: Any> Binder.bind(): AnnotatedBindingBuilder<T> = bind(T::class.java)
 
-inline infix fun <reified T: Any, TKClass: KClass<out T>> AnnotatedBindingBuilder<T>.toOptional(kClass: TKClass?) =
-        kClass?.let { to(it.java) }
+inline fun <reified T: Any> Binder.lazyBind(): Lazy<AnnotatedBindingBuilder<T>> = lazy { bind(T::class.java) }
+
+inline infix fun <reified T: Any, TKClass: KClass<out T>> Lazy<AnnotatedBindingBuilder<T>>.toOptional(kClass: TKClass?) =
+        kClass?.let { value toType it }
 
 inline infix fun <reified T: Any, TKClass: KClass<out T>> AnnotatedBindingBuilder<T>.toType(kClass: TKClass) = to(kClass.java)