1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
package org.jetbrains.dokka.plugability
import org.jetbrains.dokka.utilities.DokkaLogger
import org.jetbrains.dokka.EnvironmentAndFacade
import org.jetbrains.dokka.pages.PlatformData
import java.io.File
import java.net.URLClassLoader
import java.util.*
import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance
interface DokkaExtensionHandler {
operator fun <T, E> get(point: E, askDefault: AskDefault = AskDefault.WhenEmpty): List<T>
where T : Any, E : ExtensionPoint<T>
}
interface DokkaContext : DokkaExtensionHandler {
fun <T : DokkaPlugin> plugin(kclass: KClass<T>): T?
val logger: DokkaLogger
val platforms: Map<PlatformData, EnvironmentAndFacade>
companion object {
fun create(
pluginsClasspath: Iterable<File>,
logger: DokkaLogger,
platforms: Map<PlatformData, EnvironmentAndFacade>
): DokkaContext =
DokkaContextConfigurationImpl(logger, DefaultExtensions, platforms).apply {
pluginsClasspath.map { it.relativeTo(File(".").absoluteFile).toURI().toURL() }
.toTypedArray()
.let { URLClassLoader(it, this.javaClass.classLoader) }
.also { checkClasspath(it) }
.let { ServiceLoader.load(DokkaPlugin::class.java, it) }
.forEach { install(it) }
}.also { it.logInitialisationInfo() }
}
}
fun <T, E> DokkaContext.single(point: E): T where T : Any, E : ExtensionPoint<T> {
fun throwBadArity(substitution: String): Nothing = throw IllegalStateException(
"$point was expected to have exactly one extension registered, but $substitution found."
)
val extensions = get(point, AskDefault.WhenEmpty)
return when (extensions.size) {
0 -> throwBadArity("none was")
1 -> extensions.first()
else -> throwBadArity("multiple were")
}
}
interface DokkaContextConfiguration {
fun addExtension(extension: Extension<*>)
}
private class DokkaContextConfigurationImpl(
override val logger: DokkaLogger,
private val defaultHandler: DokkaExtensionHandler?,
override val platforms: Map<PlatformData, EnvironmentAndFacade>
) : DokkaContext, DokkaContextConfiguration {
private val plugins = mutableMapOf<KClass<*>, DokkaPlugin>()
private val pluginStubs = mutableMapOf<KClass<*>, DokkaPlugin>()
internal val extensions = mutableMapOf<ExtensionPoint<*>, MutableList<Extension<*>>>()
@Suppress("UNCHECKED_CAST")
override operator fun <T, E> get(point: E, askDefault: AskDefault) where T : Any, E : ExtensionPoint<T> =
when (askDefault) {
AskDefault.Never -> actions(point).orEmpty()
AskDefault.Always -> actions(point).orEmpty() + defaultHandler?.get(point, askDefault).orEmpty()
AskDefault.WhenEmpty ->
actions(point)?.takeIf { it.isNotEmpty() } ?: defaultHandler?.get(point, askDefault).orEmpty()
} as List<T>
private fun <E : ExtensionPoint<*>> actions(point: E) = extensions[point]?.map { it.action }
@Suppress("UNCHECKED_CAST")
override fun <T : DokkaPlugin> plugin(kclass: KClass<T>) = (plugins[kclass] ?: pluginStubFor(kclass)) as T
private fun <T : DokkaPlugin> pluginStubFor(kclass: KClass<T>): DokkaPlugin =
pluginStubs.getOrPut(kclass) { kclass.createInstance().also { it.context = this } }
fun install(plugin: DokkaPlugin) {
plugins[plugin::class] = plugin
plugin.context = this
plugin.internalInstall(this)
}
override fun addExtension(extension: Extension<*>) {
extensions.getOrPut(extension.extensionPoint, ::mutableListOf) += extension
}
fun logInitialisationInfo() {
val pluginNames = plugins.values.map { it::class.qualifiedName.toString() }
val loadedListForDebug = extensions.run { keys + values.flatten() }.toList()
.joinToString(prefix = "[\n", separator = ",\n", postfix = "\n]") { "\t$it" }
logger.progress("Loaded plugins: $pluginNames")
logger.progress("Loaded: $loadedListForDebug")
}
}
private fun checkClasspath(classLoader: URLClassLoader) {
classLoader.findResource(DokkaContext::class.java.name.replace('.', '/') + ".class")?.also {
throw AssertionError(
"Dokka API found on plugins classpath. This will lead to subtle bugs. " +
"Please fix your plugins dependencies or exclude dokka api artifact from plugin classpath"
)
}
}
enum class AskDefault {
Always, Never, WhenEmpty
}
|