aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/kotlin/resolvers
diff options
context:
space:
mode:
authorKamil Doległo <kamilok1965@interia.pl>2019-10-29 11:46:04 +0100
committerKamil Doległo <kamilok1965@interia.pl>2019-10-29 15:49:53 +0100
commit14a290009098b777521b1dedb551047fb66ba73b (patch)
treeae68219051b080ff8888308d5521aaeea7431fb4 /core/src/main/kotlin/resolvers
parent5f358199788fefb78f5db7791e718480793a77fc (diff)
downloaddokka-14a290009098b777521b1dedb551047fb66ba73b.tar.gz
dokka-14a290009098b777521b1dedb551047fb66ba73b.tar.bz2
dokka-14a290009098b777521b1dedb551047fb66ba73b.zip
[WIP] new model
Diffstat (limited to 'core/src/main/kotlin/resolvers')
-rw-r--r--core/src/main/kotlin/resolvers/DefaultLocationProvider.kt80
-rw-r--r--core/src/main/kotlin/resolvers/ExternalLocationProvider.kt98
-rw-r--r--core/src/main/kotlin/resolvers/LocationProvider.kt10
3 files changed, 188 insertions, 0 deletions
diff --git a/core/src/main/kotlin/resolvers/DefaultLocationProvider.kt b/core/src/main/kotlin/resolvers/DefaultLocationProvider.kt
new file mode 100644
index 00000000..6120e65e
--- /dev/null
+++ b/core/src/main/kotlin/resolvers/DefaultLocationProvider.kt
@@ -0,0 +1,80 @@
+package org.jetbrains.dokka.resolvers
+
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.htmlEscape
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.pages.*
+
+open class DefaultLocationProvider(private val pageGraphRoot: PageNode, val configuration: DokkaConfiguration, val extension: String): LocationProvider { // TODO: cache
+ override fun resolve(node: PageNode): String = pathTo(node) + extension
+
+ override fun resolve(dri: DRI, platforms: List<PlatformData>): String {
+ findInPageGraph(dri, platforms)?.let { return resolve(it) }
+ // Not found in PageGraph, that means it's an external link
+
+ val externalDocs = configuration.passesConfigurations
+ .filter { passConfig -> passConfig.targets.toSet() == platforms.toSet() } // TODO: change targets to something better?
+ .flatMap { it.externalDocumentationLinks }.map { it.packageListUrl }.distinct()
+
+ return ExternalLocationProvider.getLocation(dri, externalDocs)
+ }
+
+ protected open fun findInPageGraph(dri: DRI, platforms: List<PlatformData>): PageNode? = pageGraphRoot.dfs { it.dri == dri }
+
+ protected open fun pathTo(node: PageNode): String { // TODO: can be refactored probably, also we should think about root
+ fun parentPath(parent: PageNode?): String {
+ if(parent == null) return ""
+ val parts = parent.parent?.let(::parentPath) ?: ""
+ return if(parent is PackagePageNode) {"$parts/${parent.name}"} else { "$parts/${identifierToFilename(parent.name)}" }
+ }
+
+ return parentPath(node.parent) + "/${identifierToFilename(node.name)}" +
+ if (node.children.isEmpty()) {
+ ""
+ } else {
+ "/index"
+ }
+ }
+}
+
+fun DRI.toJavadocLocation(jdkVersion: Int): String { // TODO: classes without packages?
+ val packageLink = packageName?.replace(".", "/")
+ if (classNames == null) {
+ return "$packageLink/package-summary.html".htmlEscape()
+ }
+ val classLink = if (packageLink == null) { "$classNames.html" } else { "$packageLink/$classNames.html" }
+ if (callable == null) {
+ return classLink.htmlEscape()
+ }
+
+ val callableLink = "$classLink#${callable.name}" + when {
+ jdkVersion < 8 -> "(${callable.params.joinToString(", ")})"
+ jdkVersion < 10 -> "-${callable.params.joinToString("-")}-"
+ else -> "(${callable.params.joinToString(",")})"
+ }
+
+ return callableLink.htmlEscape()
+}
+
+fun DRI.toDokkaLocation(extension: String): String { // TODO: classes without packages?
+ if (classNames == null) {
+ return "$packageName/index$extension"
+ }
+ val classLink = if (packageName == null) { "" } else { "$packageName/" } +
+ classNames.split('.').joinToString("/", transform = ::identifierToFilename)
+
+ if (callable == null) {
+ return "$classLink/index$extension"
+ }
+
+ return "$classLink/${identifierToFilename(callable.name)}$extension"
+}
+
+private val reservedFilenames = setOf("index", "con", "aux", "lst", "prn", "nul", "eof", "inp", "out")
+
+private fun identifierToFilename(name: String): String {
+ if (name.isEmpty()) return "--root--"
+ val escaped = name.replace('<', '-').replace('>', '-')
+ val lowercase = escaped.replace("[A-Z]".toRegex()) { matchResult -> "-" + matchResult.value.toLowerCase() }
+ return if (lowercase in reservedFilenames) "--$lowercase--" else lowercase
+}
diff --git a/core/src/main/kotlin/resolvers/ExternalLocationProvider.kt b/core/src/main/kotlin/resolvers/ExternalLocationProvider.kt
new file mode 100644
index 00000000..e9181148
--- /dev/null
+++ b/core/src/main/kotlin/resolvers/ExternalLocationProvider.kt
@@ -0,0 +1,98 @@
+package org.jetbrains.dokka.resolvers
+
+import org.jetbrains.dokka.links.DRI
+import java.net.HttpURLConnection
+import java.net.URL
+import java.net.URLConnection
+
+object ExternalLocationProvider { // TODO: Refactor this!!!
+ private const val DOKKA_PARAM_PREFIX = "\$dokka."
+
+ private val cache: MutableMap<URL, LocationInfo> = mutableMapOf()
+
+ fun getLocation(dri: DRI, packageLocations: List<URL>): String {
+ val toResolve: MutableList<URL> = mutableListOf()
+ for(url in packageLocations){
+ val info = cache[url]
+ if(info == null) {
+ toResolve.add(url)
+ } else if(info.packages.contains(dri.packageName)) {
+ return getLink(dri, info)
+ }
+ }
+ // Not in cache, resolve packageLists
+ while (toResolve.isNotEmpty()){
+ val loc = toResolve.first().also { toResolve.remove(it) }
+ val locationInfo = loadPackageList(loc)
+ if(locationInfo.packages.contains(dri.packageName)) {
+ return getLink(dri, locationInfo)
+ }
+ }
+ return ""
+ }
+
+ private fun getLink(dri: DRI, locationInfo: LocationInfo): String = when(locationInfo.format) {
+ "javadoc" -> dri.toJavadocLocation(8)
+ "kotlin-website-html", "html" -> locationInfo.locations[dri.packageName + "." + dri.classNames] ?: dri.toDokkaLocation(".html")
+ "markdown" -> locationInfo.locations[dri.packageName + "." + dri.classNames] ?: dri.toDokkaLocation(".md")
+ // TODO: rework this
+ else -> throw RuntimeException("Unrecognized format")
+ }
+
+
+ private fun loadPackageList(url: URL): LocationInfo {
+ val packageListStream = url.doOpenConnectionToReadContent().getInputStream()
+ val (params, packages) =
+ packageListStream
+ .bufferedReader()
+ .useLines { lines -> lines.partition { it.startsWith(DOKKA_PARAM_PREFIX) } }
+
+ val paramsMap = params.asSequence()
+ .map { it.removePrefix(DOKKA_PARAM_PREFIX).split(":", limit = 2) }
+ .groupBy({ (key, _) -> key }, { (_, value) -> value })
+
+ val format = paramsMap["format"]?.singleOrNull() ?: "javadoc"
+
+ val locations = paramsMap["location"].orEmpty()
+ .map { it.split("\u001f", limit = 2) }
+ .map { (key, value) -> key to value }
+ .toMap()
+
+ val info = LocationInfo(format, packages.toSet(), locations)
+ cache[url] = info
+ return info
+ }
+
+ private fun URL.doOpenConnectionToReadContent(timeout: Int = 10000, redirectsAllowed: Int = 16): URLConnection {
+ val connection = this.openConnection().apply {
+ connectTimeout = timeout
+ readTimeout = timeout
+ }
+
+ when (connection) {
+ is HttpURLConnection -> {
+ return when (connection.responseCode) {
+ in 200..299 -> {
+ connection
+ }
+ HttpURLConnection.HTTP_MOVED_PERM,
+ HttpURLConnection.HTTP_MOVED_TEMP,
+ HttpURLConnection.HTTP_SEE_OTHER -> {
+ if (redirectsAllowed > 0) {
+ val newUrl = connection.getHeaderField("Location")
+ URL(newUrl).doOpenConnectionToReadContent(timeout, redirectsAllowed - 1)
+ } else {
+ throw RuntimeException("Too many redirects")
+ }
+ }
+ else -> {
+ throw RuntimeException("Unhandled http code: ${connection.responseCode}")
+ }
+ }
+ }
+ else -> return connection
+ }
+ }
+ data class LocationInfo(val format: String, val packages: Set<String>, val locations: Map<String, String>)
+
+}
diff --git a/core/src/main/kotlin/resolvers/LocationProvider.kt b/core/src/main/kotlin/resolvers/LocationProvider.kt
new file mode 100644
index 00000000..b62aa999
--- /dev/null
+++ b/core/src/main/kotlin/resolvers/LocationProvider.kt
@@ -0,0 +1,10 @@
+package org.jetbrains.dokka.resolvers
+
+import org.jetbrains.dokka.links.DRI
+import org.jetbrains.dokka.pages.PageNode
+import org.jetbrains.dokka.pages.PlatformData
+
+interface LocationProvider {
+ fun resolve(dri: DRI, platforms: List<PlatformData>): String
+ fun resolve(node: PageNode): String
+}