Load all resources in RuntimePackagePartProvider; optimize code

The issue was reproducible when the same package is present in different
modules with the same -module-name (which is a popular case of src/test
roots in simple IDEA projects). The problem was in the fact that several
resource files containing package name mapping with the same name were
present in the classpath, but RuntimePackagePartProvider only considered
the first one. The fix is to use getResources instead of
getResourceAsStream and handle each returned resource.

Also, optimize internal representation to store the mapping in the form
which is the most convenient for findPackageParts, which should be
faster than registerModule because in theory, it's called more often.

 #KT-21973 Fixed
 #KT-24651
This commit is contained in:
Alexander Udalov
2018-06-29 17:36:08 +02:00
parent 8ccbbf71ec
commit 4f2fdd1c01
4 changed files with 119 additions and 14 deletions
@@ -21,27 +21,44 @@ import org.jetbrains.kotlin.load.kotlin.loadModuleMapping
import org.jetbrains.kotlin.metadata.jvm.deserialization.ModuleMapping
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.serialization.deserialization.DeserializationConfiguration
import java.util.concurrent.ConcurrentHashMap
import java.io.IOException
import java.util.*
class RuntimePackagePartProvider(private val classLoader: ClassLoader) : PackagePartProvider {
private val module2Mapping = ConcurrentHashMap<String, ModuleMapping>()
// Names of modules which were registered with registerModule
private val visitedModules = hashSetOf<String>()
// Package FQ name -> list of JVM internal names of package parts in that package across all registered modules
private val packageParts = hashMapOf<String, LinkedHashSet<String>>()
@Synchronized
fun registerModule(moduleName: String) {
val mapping = try {
val resourcePath = "META-INF/$moduleName.${ModuleMapping.MAPPING_FILE_EXT}"
classLoader.getResourceAsStream(resourcePath)?.use { stream ->
ModuleMapping.loadModuleMapping(stream.readBytes(), resourcePath, DeserializationConfiguration.Default)
}
} catch (e: Exception) {
// TODO: do not swallow this exception?
null
if (!visitedModules.add(moduleName)) return
val resourcePath = "META-INF/$moduleName.${ModuleMapping.MAPPING_FILE_EXT}"
val resources = try {
classLoader.getResources(resourcePath)
} catch (e: IOException) {
EmptyEnumeration
}
for (resource in resources) {
try {
resource.openStream()?.use { stream ->
val mapping = ModuleMapping.loadModuleMapping(stream.readBytes(), resourcePath, DeserializationConfiguration.Default)
for ((packageFqName, parts) in mapping.packageFqName2Parts) {
packageParts.getOrPut(packageFqName) { linkedSetOf() }.addAll(parts.parts)
}
}
} catch (e: Exception) {
// TODO: do not swallow this exception?
}
}
module2Mapping.putIfAbsent(moduleName, mapping ?: ModuleMapping.EMPTY)
}
override fun findPackageParts(packageFqName: String): List<String> {
return module2Mapping.values.mapNotNull { it.findPackageParts(packageFqName) }.flatMap { it.parts }.distinct()
}
@Synchronized
override fun findPackageParts(packageFqName: String): List<String> =
packageParts[packageFqName]?.toList().orEmpty()
// TODO
override fun findMetadataPackageParts(packageFqName: String): List<String> = TODO()
@@ -50,4 +67,9 @@ class RuntimePackagePartProvider(private val classLoader: ClassLoader) : Package
// TODO: load annotations from resource files
return emptyList()
}
private object EmptyEnumeration : Enumeration<Nothing> {
override fun hasMoreElements(): Boolean = false
override fun nextElement(): Nothing = throw NoSuchElementException()
}
}