11

Is there a way to get list of all files in "resources" folder in Kotlin?

I can read specific file as

Application::class.java.getResourceAsStream("/folder/filename.ext")

But sometimes I just want to extract everything from folder "folder" to an external directory.

Zoe
  • 27,060
  • 21
  • 118
  • 148
Michael
  • 1,014
  • 2
  • 15
  • 26

4 Answers4

15

As I struggled with the same issue and couldn't find a concrete answer, so I had to write one myself.

Here is my solution:

fun getAllFilesInResources()
{
    val projectDirAbsolutePath = Paths.get("").toAbsolutePath().toString()
    val resourcesPath = Paths.get(projectDirAbsolutePath, "/src/main/resources")
    val paths = Files.walk(resourcesPath)
                    .filter { item -> Files.isRegularFile(item) }
                    .filter { item -> item.toString().endsWith(".txt") }
                    .forEach { item -> println("filename: $item") }
}

Here I have parsed through all the files in the /src/main/resources folder and then filtered only the regular files (no directories included) and then filtered for the text files within the resources directory.

The output is a list of all the absolute file paths with the extension .txt in the resources folder. Now you can use these paths to copy the files to an external folder.

Zoe
  • 27,060
  • 21
  • 118
  • 148
vivek86
  • 707
  • 9
  • 18
  • I am not quite sure what you mean by debug and jar? did you mean filtering jar files? in that case all you need to do is replace the .txt with .jar and you will filter only jar files. – vivek86 Jan 23 '20 at 08:32
  • 3
    When you pack to a jar the procedure in Java is different. I guess this is applicable for Kotlin as well. – Michael Jan 23 '20 at 11:02
  • 4
    Refering to `"/src/main/resources"` will not work within a bundled JAR file – Simon Forsberg Apr 04 '21 at 22:17
  • Used this to write a file to the resources directory in Compose Desktop, with one change: " val projectDirAbsolutePath = Paths.get("").toAbsolutePath().toString().plus("/src/jvmMain/resources") " – Matt Grier Mar 17 '23 at 00:18
  • You should NOT refer to /src - this is never a good solution. – Zordid Apr 11 '23 at 12:00
6

Two distinct parts:

  1. Obtain a file that represents the resource directory
  2. Traverse the directory

For 1 you can use Java's getResource:

val dir = File( object {}.javaClass.getResource(directoryPath).file )

For 2 you can use Kotlin's File.walk extension function that returns a sequence of files which you can process, e.g:

dir.walk().forEach { f ->
    if(f.isFile) {
        println("file ${f.name}")
    } else {
        println("dir ${f.name}")
    }
}

Put together you may end up with the following code:

fun onEachResource(path: String, action: (File) -> Unit) {

    fun resource2file(path: String): File {
        val resourceURL = object {}.javaClass.getResource(path)
        return File(checkNotNull(resourceURL, { "Path not found: '$path'" }).file)
    }

    with(resource2file(path)) {
        this.walk().forEach { f -> action(f) }
    }
}

so that if you have resources/nested direcory, you can:

fun main() {
    
    val print = { f: File ->
        when (f.isFile) {
            true -> println("[F] ${f.absolutePath}")
            false -> println("[D] ${f.absolutePath}")
        }
    }
    
    onEachResource("/nested", print)
}
David Soroko
  • 8,521
  • 2
  • 39
  • 51
5

There are no methods for it (i.e. Application::class.java.listFilesInDirectory("/folder/")), but you can create your own system to list the files in a directory:

@Throws(IOException::class)
fun getResourceFiles(path: String): List<String> = getResourceAsStream(path).use{
    return if(it == null) emptyList()
    else BufferedReader(InputStreamReader(it)).readLines()
}

private fun getResourceAsStream(resource: String): InputStream? = 
        Thread.currentThread().contextClassLoader.getResourceAsStream(resource) 
                ?: resource::class.java.getResourceAsStream(resource)

Then just call getResourceFiles("/folder/") and you'll get a list of files in the folder, assuming it's in the classpath.

This works because Kotlin has an extension function that reads lines into a List of Strings. The declaration is:

/**
 * Reads this reader content as a list of lines.
 *
 * Do not use this function for huge files.
 */
public fun Reader.readLines(): List<String> {
    val result = arrayListOf<String>()
    forEachLine { result.add(it) }
    return result
}
Zoe
  • 27,060
  • 21
  • 118
  • 148
  • Am I wrong that your function can be reduced to ` private fun getResourceList(path: String): List { val stream = Application::class.java.getResourceAsStream(path) ?: return emptyList() return stream.bufferedReader().use { it.readLines() } } ` – Michael Mar 22 '18 at 10:38
  • `resource::class.java` will always refer to `String::class.java` which might not be what you intended – Simon Forsberg Apr 04 '21 at 22:32
  • @SimonForsberg I have no idea what I was thinking. It's still only a fallback at least, so `Thread.currentThread().contextClassLoader.getResourceAsStream(resource)` at least takes precedence over whatever I thought of there – Zoe Apr 04 '21 at 22:57
1

Here is a solution to iterate over JAR-packed resources on JVM:

fun iterateResources(resourceDir: String) {
    val resource = MethodHandles.lookup().lookupClass().classLoader.getResource(resourceDir)
        ?: error("Resource $resourceDir was not found")
    FileSystems.newFileSystem(resource.toURI(), emptyMap<String, String>()).use { fs ->
        Files.walk(fs.getPath(resourceDir))
            .filter { it.extension == "ttf" }
            .forEach { file -> println(file.toUri().toString()) }
    }
}
Vadzim
  • 24,954
  • 11
  • 143
  • 151