aboutsummaryrefslogtreecommitdiff
path: root/core/src
diff options
context:
space:
mode:
Diffstat (limited to 'core/src')
-rw-r--r--core/src/main/kotlin/Kotlin/DocumentationBuilder.kt4
-rw-r--r--core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt107
2 files changed, 86 insertions, 25 deletions
diff --git a/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt b/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt
index 0fc87a8e..c258ef2c 100644
--- a/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt
+++ b/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
import org.jetbrains.kotlin.types.typeUtil.supertypes
+import java.nio.file.Paths
import com.google.inject.name.Named as GuiceNamed
class DocumentationOptions(val outputDir: String,
@@ -45,7 +46,8 @@ class DocumentationOptions(val outputDir: String,
// Sorted by pattern length
perPackageOptions: List<PackageOptions> = emptyList(),
externalDocumentationLinks: List<ExternalDocumentationLink> = emptyList(),
- noStdlibLink: Boolean) {
+ noStdlibLink: Boolean,
+ val cacheRoot: String = Paths.get(System.getProperty("user.home"), ".cache", "dokka").toString()) {
init {
if (perPackageOptions.any { it.prefix == "" })
throw IllegalArgumentException("Please do not register packageOptions with all match pattern, use global settings instead")
diff --git a/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt b/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt
index 299492a4..bdf426e0 100644
--- a/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt
+++ b/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt
@@ -1,7 +1,9 @@
package org.jetbrains.dokka
import com.google.inject.Inject
+import com.google.inject.Singleton
import com.intellij.psi.PsiMethod
+import com.intellij.util.io.*
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
@@ -11,11 +13,19 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.parents
+import java.io.ByteArrayOutputStream
+import java.io.PrintWriter
import java.net.URL
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.security.MessageDigest
+fun ByteArray.toHexString() = this.joinToString(separator = "") { "%02x".format(it) }
+@Singleton
class ExternalDocumentationLinkResolver @Inject constructor(
- val options: DocumentationOptions
+ val options: DocumentationOptions,
+ val logger: DokkaLogger
) {
val packageFqNameToLocation = mutableMapOf<FqName, ExternalDocumentationRoot>()
@@ -23,41 +33,90 @@ class ExternalDocumentationLinkResolver @Inject constructor(
class ExternalDocumentationRoot(val rootUrl: URL, val resolver: InboundExternalLinkResolutionService, val locations: Map<String, String>)
- fun loadPackageLists() {
- options.externalDocumentationLinks.forEach { link ->
- val (params, packages) =
- link.packageListUrl
- .openStream()
- .bufferedReader()
- .useLines { lines -> lines.partition { it.startsWith(DOKKA_PARAM_PREFIX) } }
+ // TODO: Make configurable
+ val cacheDir: Path = Paths.get(options.cacheRoot, "packageListCache").apply { createDirectories() }
- val paramsMap = params.asSequence()
- .map { it.removePrefix(DOKKA_PARAM_PREFIX).split(":", limit = 2) }
- .groupBy({ (key, _) -> key }, { (_, value) -> value })
+ val cachedProtocols = setOf("http", "https", "ftp")
- val format = paramsMap["format"]?.singleOrNull() ?: "javadoc"
+ fun loadPackageList(link: DokkaConfiguration.ExternalDocumentationLink) {
- val locations = paramsMap["location"].orEmpty()
- .map { it.split("\u001f", limit = 2) }
- .map { (key, value) -> key to value }
- .toMap()
+ val packageListUrl = link.packageListUrl
+ val needsCache = packageListUrl.protocol in cachedProtocols
- val resolver = if (format == "javadoc") {
- InboundExternalLinkResolutionService.Javadoc()
+ val packageListStream = if (needsCache) {
+ val text = packageListUrl.toExternalForm()
+
+ val digest = MessageDigest.getInstance("SHA-256")
+ val hash = digest.digest(text.toByteArray(Charsets.UTF_8)).toHexString()
+ val cacheEntry = cacheDir.resolve(hash)
+
+ if (cacheEntry.exists()) {
+ try {
+ val connection = packageListUrl.openConnection()
+ val originModifiedDate = connection.date
+ val cacheDate = cacheEntry.lastModified().toMillis()
+ if (originModifiedDate > cacheDate || originModifiedDate == 0L) {
+ if (originModifiedDate == 0L)
+ logger.warn("No date header for $packageListUrl, downloading anyway")
+ else
+ logger.info("Renewing package-list from $packageListUrl")
+ connection.getInputStream().copyTo(cacheEntry.outputStream())
+ }
+ } catch(e: Exception) {
+ logger.error("Failed to update package-list cache for $link")
+ val baos = ByteArrayOutputStream()
+ PrintWriter(baos).use {
+ e.printStackTrace(it)
+ }
+ baos.flush()
+ logger.error(baos.toString())
+ }
} else {
- val linkExtension = paramsMap["linkExtension"]?.singleOrNull() ?:
- throw RuntimeException("Failed to parse package list from ${link.packageListUrl}")
- InboundExternalLinkResolutionService.Dokka(linkExtension)
+ logger.info("Downloading package-list from $packageListUrl")
+ packageListUrl.openStream().copyTo(cacheEntry.outputStream())
}
+ cacheEntry.inputStream()
+ } else {
+ packageListUrl.openStream()
+ }
+
+ val (params, packages) =
+ packageListStream
+ .bufferedReader()
+ .useLines { lines -> lines.partition { it.startsWith(DOKKA_PARAM_PREFIX) } }
- val rootInfo = ExternalDocumentationRoot(link.url, resolver, locations)
+ val paramsMap = params.asSequence()
+ .map { it.removePrefix(DOKKA_PARAM_PREFIX).split(":", limit = 2) }
+ .groupBy({ (key, _) -> key }, { (_, value) -> value })
- packages.map { FqName(it) }.forEach { packageFqNameToLocation[it] = rootInfo }
+ 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 resolver = if (format == "javadoc") {
+ InboundExternalLinkResolutionService.Javadoc()
+ } else {
+ val linkExtension = paramsMap["linkExtension"]?.singleOrNull() ?:
+ throw RuntimeException("Failed to parse package list from $packageListUrl")
+ InboundExternalLinkResolutionService.Dokka(linkExtension)
}
+
+ val rootInfo = ExternalDocumentationRoot(link.url, resolver, locations)
+
+ packages.map { FqName(it) }.forEach { packageFqNameToLocation[it] = rootInfo }
}
init {
- loadPackageLists()
+ options.externalDocumentationLinks.forEach {
+ try {
+ loadPackageList(it)
+ } catch (e: Exception) {
+ throw RuntimeException("Exception while loading package-list from $it", e)
+ }
+ }
}
fun buildExternalDocumentationLink(symbol: DeclarationDescriptor): String? {