Skip to content

Commit

Permalink
Fix hot reload on latest JDKs to work without any special options.
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergey Mashkov committed Jan 10, 2020
1 parent afc167a commit b7a12cf
Showing 1 changed file with 33 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ internal fun ClassLoader.allURLs(): Set<URL> {
/**
* This only works in JDK9+ with VM option `--add-opens java.base/jdk.internal.loader=ALL-UNNAMED`
* This is required since [allURLs] function is unable to lookup url list due to modules and class loaders
* reorganisation in JDK9+.
* reorganisation in JDK9+. However, if failed, it fallbacks to [urlClassPathByPackagesList] implementation
* that should always work.
*/
private fun ClassLoader.urlClassPath(): List<URL>? {
try {
Expand All @@ -39,12 +40,41 @@ private fun ClassLoader.urlClassPath(): List<URL>? {
val urls = getURLsMethod.invoke(ucpInstance) as Array<URL>?

return urls?.toList()
} catch (cause: Throwable) {
return null
} catch (_: Throwable) {
return try {
urlClassPathByPackagesList()
} catch (_: Throwable) {
null
}
}
}

/**
* Extract classloader's packages list and guess URLs by package segments.
* Unlike the old way, this doesn't require any black magic so works well on all JDKs
* from JDK6 to the latest.
*/
private fun ClassLoader.urlClassPathByPackagesList(): List<URL>? {
val allPackagePaths = ClassLoaderDelegate(this).packagesList().map { it.replace('.', '/') }
.flatMapTo(HashSet<String>()) { packageName ->
val segments = packageName.split('/')
(1 .. segments.size).
map { segments.subList(0, it).joinToString("/") } + packageName
}.sortedBy { it.count { character -> character == '/' } } + ""

return allPackagePaths.flatMap { path -> getResources(path)?.toList() ?: emptyList() }
.distinctBy { it.path.substringBefore('!') }
}

private fun Class<*>.findURLClassPathField(): Field? {
declaredFields.firstOrNull { it.name == "ucp" && it.type.simpleName == "URLClassPath" }?.let { return it }
return superclass?.findURLClassPathField() ?: return null
}

/**
* This is auxillary classloader that is not used for loading classes. The purpose is just
* to get access to [getPackages] function that is unfortunately protected.
*/
private class ClassLoaderDelegate(delegate: ClassLoader) : ClassLoader(delegate) {
internal fun packagesList(): List<String> = getPackages().map { it.name }
}

0 comments on commit b7a12cf

Please sign in to comment.