aboutsummaryrefslogtreecommitdiff
path: root/src/Utilities
diff options
context:
space:
mode:
authorSergey Mashkov <sergey.mashkov@jetbrains.com>2015-07-31 15:35:34 +0300
committerDmitry Jemerov <yole@jetbrains.com>2015-10-29 13:21:47 +0100
commitc9d59e9ae85f76e021d53c77ef18bfce0ff7ec7c (patch)
treed5e86994c762318008f7beaf781cdeafde573802 /src/Utilities
parentff77b8e0ad0b5089e940227dfdd94ba21cfc6bd8 (diff)
downloaddokka-c9d59e9ae85f76e021d53c77ef18bfce0ff7ec7c.tar.gz
dokka-c9d59e9ae85f76e021d53c77ef18bfce0ff7ec7c.tar.bz2
dokka-c9d59e9ae85f76e021d53c77ef18bfce0ff7ec7c.zip
Use Guice injector and ServiceLocator to load implementations on the fly
Diffstat (limited to 'src/Utilities')
-rw-r--r--src/Utilities/GuiceModule.kt58
-rw-r--r--src/Utilities/ServiceLocator.kt105
2 files changed, 163 insertions, 0 deletions
diff --git a/src/Utilities/GuiceModule.kt b/src/Utilities/GuiceModule.kt
new file mode 100644
index 00000000..57bad468
--- /dev/null
+++ b/src/Utilities/GuiceModule.kt
@@ -0,0 +1,58 @@
+package org.jetbrains.dokka.Utilities
+
+import com.google.inject.Binder
+import com.google.inject.Module
+import com.google.inject.Provider
+import com.google.inject.name.Names
+import org.jetbrains.dokka.*
+import org.jetbrains.dokka.Formats.FormatDescriptor
+import java.io.File
+
+class GuiceModule(val config: DokkaGenerator) : Module {
+ override fun configure(binder: Binder) {
+ binder.bind(javaClass<DokkaGenerator>()).toInstance(config)
+ binder.bind(javaClass<File>()).annotatedWith(Names.named("outputDir")).toInstance(File(config.outputDir))
+
+ binder.bindNameAnnotated<LocationService, SingleFolderLocationService>("singleFolder")
+ binder.bindNameAnnotated<FileLocationService, SingleFolderLocationService>("singleFolder")
+ binder.bindNameAnnotated<LocationService, FoldersLocationService>("folders")
+ binder.bindNameAnnotated<FileLocationService, FoldersLocationService>("folders")
+
+ // defaults
+ binder.bind(javaClass<LocationService>()).to(javaClass<FoldersLocationService>())
+ binder.bind(javaClass<FileLocationService>()).to(javaClass<FoldersLocationService>())
+ binder.bind(javaClass<LanguageService>()).to(javaClass<KotlinLanguageService>())
+
+ binder.bind(javaClass<HtmlTemplateService>()).toProvider(object : Provider<HtmlTemplateService> {
+ override fun get(): HtmlTemplateService = HtmlTemplateService.default("/dokka/styles/style.css")
+ })
+
+ binder.registerCategory<LanguageService>("language")
+ binder.registerCategory<OutlineFormatService>("outline")
+ binder.registerCategory<FormatService>("format")
+ binder.registerCategory<Generator>("generator")
+
+ val descriptor = ServiceLocator.lookup<FormatDescriptor>("format", config.outputFormat, config)
+
+ descriptor.outlineServiceClass?.let { clazz ->
+ binder.bind(javaClass<OutlineFormatService>()).to(clazz)
+ }
+ descriptor.formatServiceClass?.let { clazz ->
+ binder.bind(javaClass<FormatService>()).to(clazz)
+ }
+ binder.bind(javaClass<Generator>()).to(descriptor.generatorServiceClass)
+ }
+
+}
+
+private inline fun <reified T: Any> Binder.registerCategory(category: String) {
+ ServiceLocator.allServices(category).forEach {
+ @Suppress("UNCHECKED_CAST")
+ bind(javaClass<T>()).annotatedWith(Names.named(it.name)).to(javaClass<T>().classLoader.loadClass(it.className) as Class<T>)
+ }
+}
+
+private inline fun <reified Base : Any, reified T : Base> Binder.bindNameAnnotated(name: String) {
+ bind(javaClass<Base>()).annotatedWith(Names.named(name)).to(javaClass<T>())
+}
+
diff --git a/src/Utilities/ServiceLocator.kt b/src/Utilities/ServiceLocator.kt
new file mode 100644
index 00000000..bc04238f
--- /dev/null
+++ b/src/Utilities/ServiceLocator.kt
@@ -0,0 +1,105 @@
+package org.jetbrains.dokka.Utilities
+
+import org.jetbrains.dokka.DokkaGenerator
+import java.io.File
+import java.util.*
+import java.util.jar.JarFile
+import java.util.zip.ZipEntry
+
+data class ServiceDescriptor(val name: String, val category: String, val description: String?, val className: String)
+
+class ServiceLookupException(message: String) : Exception(message)
+
+public object ServiceLocator {
+ public fun <T : Any> lookup(clazz: Class<T>, category: String, implementationName: String, conf: DokkaGenerator): T {
+ val descriptor = lookupDescriptor(category, implementationName)
+ val loadedClass = javaClass.classLoader.loadClass(descriptor.className)
+ val constructor = loadedClass.constructors
+ .filter { it.parameterTypes.isEmpty() || (it.parameterTypes.size() == 1 && conf.javaClass.isInstance(it.parameterTypes[0])) }
+ .sortedByDescending { it.parameterTypes.size() }
+ .firstOrNull() ?: throw ServiceLookupException("Class ${descriptor.className} has no corresponding constructor")
+
+ val implementationRawType: Any = if (constructor.parameterTypes.isEmpty()) constructor.newInstance() else constructor.newInstance(constructor)
+
+ if (!clazz.isInstance(implementationRawType)) {
+ throw ServiceLookupException("Class ${descriptor.className} is not a subtype of ${clazz.name}")
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ return implementationRawType as T
+ }
+
+ public fun <T> lookupClass(clazz: Class<T>, category: String, implementationName: String): Class<T> = lookupDescriptor(category, implementationName).className.let { className ->
+ javaClass.classLoader.loadClass(className).let { loaded ->
+ if (!clazz.isAssignableFrom(loaded)) {
+ throw ServiceLookupException("Class $className is not a subtype of ${clazz.name}")
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ val casted = loaded as Class<T>
+
+ casted
+ }
+ }
+
+ private fun lookupDescriptor(category: String, implementationName: String): ServiceDescriptor {
+ val properties = javaClass.classLoader.getResourceAsStream("dokka/$category/$implementationName.properties")?.use { stream ->
+ Properties().let { properties ->
+ properties.load(stream)
+ properties
+ }
+ } ?: throw ServiceLookupException("No implementation with name $implementationName found in category $category")
+
+ val className = properties["class"]?.toString() ?: throw ServiceLookupException("Implementation $implementationName has no class configured")
+
+ return ServiceDescriptor(implementationName, category, properties["description"]?.toString(), className)
+ }
+
+ fun allServices(category: String): List<ServiceDescriptor> = javaClass.classLoader.getResourceAsStream("dokka/$category")?.use { stream ->
+ val entries = this.javaClass.classLoader.getResources("dokka/$category")?.toList() ?: emptyList()
+
+ entries.flatMap {
+ when (it.protocol) {
+ "file" -> File(it.file).listFiles()?.filter { it.extension == "properties" }?.map { lookupDescriptor(category, it.nameWithoutExtension) } ?: emptyList()
+ "jar" -> {
+ val file = JarFile(it.file.removePrefix("file:").substringBefore("!"))
+ try {
+ val jarPath = it.file.substringAfterLast("!").removePrefix("/")
+ file.entries()
+ .asSequence()
+ .filter { entry -> !entry.isDirectory && entry.path == jarPath && entry.extension == "properties" }
+ .map { entry ->
+ lookupDescriptor(category, entry.fileName.substringBeforeLast("."))
+ }.toList()
+ } finally {
+ file.close()
+ }
+ }
+ else -> emptyList<ServiceDescriptor>()
+ }
+ }
+ } ?: emptyList()
+}
+
+public inline fun <reified T : Any> ServiceLocator.lookup(category: String, implementationName: String, conf: DokkaGenerator): T = lookup(javaClass<T>(), category, implementationName, conf)
+public inline fun <reified T : Any> ServiceLocator.lookupClass(category: String, implementationName: String): Class<T> = lookupClass(javaClass<T>(), category, implementationName)
+public inline fun <reified T : Any> ServiceLocator.lookupOrNull(category: String, implementationName: String, conf: DokkaGenerator): T? = try {
+ lookup(javaClass<T>(), category, implementationName, conf)
+} catch (any: Throwable) {
+ null
+}
+
+fun main(args: Array<String>) {
+ ServiceLocator.allServices("format").forEach {
+ println(it)
+ }
+}
+
+private val ZipEntry.fileName: String
+ get() = name.substringAfterLast("/", name)
+
+private val ZipEntry.path: String
+ get() = name.substringBeforeLast("/", "").removePrefix("/")
+
+private val ZipEntry.extension: String?
+ get() = fileName.let { fn -> if ("." in fn) fn.substringAfterLast(".") else null }