<lambda>null1 package org.jetbrains.dokka.Utilities
2
3 import java.io.File
4 import java.net.URISyntaxException
5 import java.net.URL
6 import java.util.*
7 import java.util.jar.JarFile
8 import java.util.zip.ZipEntry
9
10 data class ServiceDescriptor(val name: String, val category: String, val description: String?, val className: String)
11
12 class ServiceLookupException(message: String) : Exception(message)
13
14 object ServiceLocator {
15 fun <T : Any> lookup(clazz: Class<T>, category: String, implementationName: String): T {
16 val descriptor = lookupDescriptor(category, implementationName)
17 return lookup(clazz, descriptor)
18 }
19
20 fun <T : Any> lookup(
21 clazz: Class<T>,
22 descriptor: ServiceDescriptor
23 ): T {
24 val loadedClass = javaClass.classLoader.loadClass(descriptor.className)
25 val constructor = loadedClass.constructors
26 .filter { it.parameterTypes.isEmpty() }
27 .firstOrNull()
28 ?: throw ServiceLookupException("Class ${descriptor.className} has no corresponding constructor")
29
30 val implementationRawType: Any =
31 if (constructor.parameterTypes.isEmpty()) constructor.newInstance() else constructor.newInstance(constructor)
32
33 if (!clazz.isInstance(implementationRawType)) {
34 throw ServiceLookupException("Class ${descriptor.className} is not a subtype of ${clazz.name}")
35 }
36
37 @Suppress("UNCHECKED_CAST")
38 return implementationRawType as T
39 }
40
41 private fun lookupDescriptor(category: String, implementationName: String): ServiceDescriptor {
42 val properties = javaClass.classLoader.getResourceAsStream("dokka/$category/$implementationName.properties")?.use { stream ->
43 Properties().let { properties ->
44 properties.load(stream)
45 properties
46 }
47 } ?: throw ServiceLookupException("No implementation with name $implementationName found in category $category")
48
49 val className = properties["class"]?.toString() ?: throw ServiceLookupException("Implementation $implementationName has no class configured")
50
51 return ServiceDescriptor(implementationName, category, properties["description"]?.toString(), className)
52 }
53
54 fun URL.toFile(): File {
55 assert(protocol == "file")
56
57 return try {
58 File(toURI())
59 } catch (e: URISyntaxException) { //Try to handle broken URLs, with unescaped spaces
60 File(path)
61 }
62 }
63
64 fun allServices(category: String): List<ServiceDescriptor> {
65 val entries = this.javaClass.classLoader.getResources("dokka/$category")?.toList() ?: emptyList()
66
67 return entries.flatMap {
68 when (it.protocol) {
69 "file" -> it.toFile().listFiles()?.filter { it.extension == "properties" }?.map { lookupDescriptor(category, it.nameWithoutExtension) } ?: emptyList()
70 "jar" -> {
71 val file = JarFile(URL(it.file.substringBefore("!")).toFile())
72 try {
73 val jarPath = it.file.substringAfterLast("!").removePrefix("/")
74 file.entries()
75 .asSequence()
76 .filter { entry -> !entry.isDirectory && entry.path == jarPath && entry.extension == "properties" }
77 .map { entry ->
78 lookupDescriptor(category, entry.fileName.substringBeforeLast("."))
79 }.toList()
80 } finally {
81 file.close()
82 }
83 }
84 else -> emptyList<ServiceDescriptor>()
85 }
86 }
87 }
88 }
89
lookupnull90 inline fun <reified T : Any> ServiceLocator.lookup(category: String, implementationName: String): T = lookup(T::class.java, category, implementationName)
91 inline fun <reified T : Any> ServiceLocator.lookup(desc: ServiceDescriptor): T = lookup(T::class.java, desc)
92
93 private val ZipEntry.fileName: String
94 get() = name.substringAfterLast("/", name)
95
96 private val ZipEntry.path: String
97 get() = name.substringBeforeLast("/", "").removePrefix("/")
98
99 private val ZipEntry.extension: String?
100 get() = fileName.let { fn -> if ("." in fn) fn.substringAfterLast(".") else null }
101