• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<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