@@ -28,6 +28,7 @@ import okio.Path.Companion.toOkioPath
2828import okio.Path.Companion.toPath
2929import okio.Sink
3030import okio.Source
31+ import okio.source
3132
3233/* *
3334 * A file system exposing Java classpath resources. It is equivalent to the files returned by
@@ -41,8 +42,9 @@ import okio.Source
4142 * This file system is read-only.
4243 */
4344internal class ResourceFileSystem internal constructor(
44- classLoader : ClassLoader ,
45+ private val classLoader : ClassLoader ,
4546 indexEagerly : Boolean ,
47+ private val systemFileSystem : FileSystem = SYSTEM ,
4648) : FileSystem() {
4749 private val roots: List <Pair <FileSystem , Path >> by lazy { classLoader.toClasspathRoots() }
4850
@@ -122,14 +124,10 @@ internal class ResourceFileSystem internal constructor(
122124
123125 override fun source (file : Path ): Source {
124126 if (! keepPath(file)) throw FileNotFoundException (" file not found: $file " )
125- val relativePath = file.toRelativePath()
126- for ((fileSystem, base) in roots) {
127- try {
128- return fileSystem.source(base / relativePath)
129- } catch (_: FileNotFoundException ) {
130- }
131- }
132- throw FileNotFoundException (" file not found: $file " )
127+ // Make sure we have a path that doesn't start with '/'.
128+ val relativePath = ROOT .resolve(file).relativeTo(ROOT )
129+ return classLoader.getResourceAsStream(relativePath.toString())?.source()
130+ ? : throw FileNotFoundException (" file not found: $file " )
133131 }
134132
135133 override fun sink (file : Path , mustCreate : Boolean ): Sink {
@@ -157,6 +155,47 @@ internal class ResourceFileSystem internal constructor(
157155 return canonicalThis.relativeTo(ROOT ).toString()
158156 }
159157
158+ /* *
159+ * Returns a search path of classpath roots. Each element contains a file system to use, and
160+ * the base directory of that file system to search from.
161+ */
162+ private fun ClassLoader.toClasspathRoots (): List <Pair <FileSystem , Path >> {
163+ // We'd like to build this upon an API like ClassLoader.getURLs() but unfortunately that
164+ // API exists only on URLClassLoader (and that isn't the default class loader implementation).
165+ //
166+ // The closest we have is `ClassLoader.getResources("")`. It returns all classpath roots that
167+ // are directories but none that are .jar files. To mitigate that we also search for all
168+ // `META-INF/MANIFEST.MF` files, hastily assuming that every .jar file will have such an
169+ // entry.
170+ //
171+ // Classpath entries that aren't directories and don't have a META-INF/MANIFEST.MF file will
172+ // not be visible in this file system.
173+ return getResources(" " ).toList().mapNotNull { it.toFileRoot() } +
174+ getResources(" META-INF/MANIFEST.MF" ).toList().mapNotNull { it.toJarRoot() }
175+ }
176+
177+ private fun URL.toFileRoot (): Pair <FileSystem , Path >? {
178+ if (protocol != " file" ) return null // Ignore unexpected URLs.
179+ return systemFileSystem to File (toURI()).toOkioPath()
180+ }
181+
182+ private fun URL.toJarRoot (): Pair <FileSystem , Path >? {
183+ val urlString = toString()
184+ if (! urlString.startsWith(" jar:file:" )) return null // Ignore unexpected URLs.
185+
186+ // Given a URL like `jar:file:/tmp/foo.jar!/META-INF/MANIFEST.MF`, get the path to the archive
187+ // file, like `/tmp/foo.jar`.
188+ val suffixStart = urlString.lastIndexOf(" !" )
189+ if (suffixStart == - 1 ) return null
190+ val path = File (URI .create(urlString.substring(" jar:" .length, suffixStart))).toOkioPath()
191+ val zip = openZip(
192+ zipPath = path,
193+ fileSystem = systemFileSystem,
194+ predicate = { entry -> keepPath(entry.canonicalPath) },
195+ )
196+ return zip to ROOT
197+ }
198+
160199 private companion object {
161200 val ROOT = " /" .toPath()
162201
@@ -165,47 +204,6 @@ internal class ResourceFileSystem internal constructor(
165204 return ROOT / (toString().removePrefix(prefix).replace(' \\ ' , ' /' ))
166205 }
167206
168- /* *
169- * Returns a search path of classpath roots. Each element contains a file system to use, and
170- * the base directory of that file system to search from.
171- */
172- fun ClassLoader.toClasspathRoots (): List <Pair <FileSystem , Path >> {
173- // We'd like to build this upon an API like ClassLoader.getURLs() but unfortunately that
174- // API exists only on URLClassLoader (and that isn't the default class loader implementation).
175- //
176- // The closest we have is `ClassLoader.getResources("")`. It returns all classpath roots that
177- // are directories but none that are .jar files. To mitigate that we also search for all
178- // `META-INF/MANIFEST.MF` files, hastily assuming that every .jar file will have such an
179- // entry.
180- //
181- // Classpath entries that aren't directories and don't have a META-INF/MANIFEST.MF file will
182- // not be visible in this file system.
183- return getResources(" " ).toList().mapNotNull { it.toFileRoot() } +
184- getResources(" META-INF/MANIFEST.MF" ).toList().mapNotNull { it.toJarRoot() }
185- }
186-
187- fun URL.toFileRoot (): Pair <FileSystem , Path >? {
188- if (protocol != " file" ) return null // Ignore unexpected URLs.
189- return SYSTEM to File (toURI()).toOkioPath()
190- }
191-
192- fun URL.toJarRoot (): Pair <FileSystem , Path >? {
193- val urlString = toString()
194- if (! urlString.startsWith(" jar:file:" )) return null // Ignore unexpected URLs.
195-
196- // Given a URL like `jar:file:/tmp/foo.jar!/META-INF/MANIFEST.MF`, get the path to the archive
197- // file, like `/tmp/foo.jar`.
198- val suffixStart = urlString.lastIndexOf(" !" )
199- if (suffixStart == - 1 ) return null
200- val path = File (URI .create(urlString.substring(" jar:" .length, suffixStart))).toOkioPath()
201- val zip = openZip(
202- zipPath = path,
203- fileSystem = SYSTEM ,
204- predicate = { entry -> keepPath(entry.canonicalPath) },
205- )
206- return zip to ROOT
207- }
208-
209207 private fun keepPath (path : Path ) = ! path.name.endsWith(" .class" , ignoreCase = true )
210208 }
211209}
0 commit comments