Computer scienceProgramming languagesKotlinAdditional instrumentsFiles

Works with file hierarchies

8 minutes read

Getting directory contents and directory's /file's parent with File and Path

The Path.listDirectoryEntries() function prints the contents (files and directories) of a chosen directory.

val helloWorld = Path("/Files/CompletedProjects/HelloWorld")
val helloWorldFilesNames = helloWorld.listDirectoryEntries().map { it.name } // [Doc.pdf, Reviews.txt]

val reviews = Path("/Files/CompletedProjects/HelloWorld/Reviews.txt")
val reviewsFiles = if (reviews.isDirectory()) reviews.listDirectoryEntries() else emptyList() // []

val soundtracks = Path("/Files/Music/SoundTracks")
val soundtracksFiles = if (soundtracks.isDirectory()) soundtracks.listDirectoryEntries() else emptyList() // []

reviewsFiles is null because reviews is not a directory at all and can't include other files or subdirectories. On the other hand, soundtracks is a directory without files, so the result is an empty array.

The Path.parent property gives you the name of a file's or directory's parent.

The Path.parentFile property provides a file's or directory's parent as a File.

val files = Path("/Files")
println(files.parent) // the result is "/"
println(files.parent.fileName) // the result is ""

val reviews = Path("/Files/CompletedProjects/HelloWorld/Reviews.txt")
println(reviews.parent) // the result is "/Files/CompletedProjects/HelloWorld"
println(reviews.parent.fileName) // the result is "HelloWorld"

val root = Path("/")
println(root.parent) // the result is "null"
println(root.parent.fileName) // This line won't be executed because root.parent is null

If a directory is the root of a file hierarchy, you will get null. Be careful to not trigger exceptions!

Iterating in Both Directions

You can use File.walkTopDown() and File.walkBottomUp() to go through a file hierarchy, from parent to children and from children to parent respectively. Also, you can use Path.walk() and use the sequence or iterable option to perform the direction.

// with File
val files: File = File("/Files")
println("TOP_DOWN: ")
files.walkTopDown().forEach { println(it) }

println("BOTTOM_UP: ")
files.walkBottomUp().forEach { println(it) }

// with Path
val dir: Path = Path("/Files")
println("TOP_DOWN: ")
dir.walk().forEach { println(it) }

println("BOTTOM_UP: ")
dir.walk().asIterable().reversed().forEach { println(it) }

The output of this program will be:

TOP_DOWN:
/Files
/Files/Music
/Files/Music/SoundTracks
/Files/CompletedProjects
/Files/CompletedProjects/HelloWorld
/Files/CompletedProjects/HelloWorld/Doc.pdf
/Files/CompletedProjects/HelloWorld/Reviews.txt
/Files/CompletedProjects/JCalculator
/Files/CompletedProjects/JCalculator/Doc.pdf
BOTTOM_UP:
/Files/Music/SoundTracks
/Files/Music
/Files/CompletedProjects/HelloWorld/Doc.pdf
/Files/CompletedProjects/HelloWorld/Reviews.txt
/Files/CompletedProjects/HelloWorld
/Files/CompletedProjects/JCalculator/Doc.pdf
/Files/CompletedProjects/JCalculator
/Files/CompletedProjects
/Files

Therefore, these three methods let us traverse the entire file structure recursively.

Working with Hierarchies

We will now get the two subdirectories containing project data.

val completedProjects: Path = Path("/Files/CompletedProjects")
val projects = Files.walk(completedProjects, 1) // HelloWorld and JCalculator

The specific order of files in the array is not promised. To find the HelloWorld project, we will traverse the file tree:

val helloWorldProject: File = projects.first { it.name == "HelloWorld" }

You can also obtain the HelloWorld directory by using the File.listFiles() method:

val helloWorldProject: File = completedProjects.listFiles().first { it.name == "HelloWorld" }

We are just assuming it's not null to simplify our code for educational purposes. But it's always better to check initially as the actual hierarchy might change.

Now, let's try to navigate to the Soundtracks directory starting from the Reviews.txt file:

val reviews = File("/Files/CompletedProjects/HelloWorld/Reviews.txt")
var parent = reviews.parentFile
while (parent.name != "Files"){
   parent = parent.parentFile
}

val soundTracks: File = parent.walkTopDown().first { it.name == "SoundTracks" }

File Copying

If you need to copy a file, use the copyTo() function:

val fileIn = Path("newFile.txt")
val fileOut = Path("copyNewFile.txt")
fileIn.copyTo(fileOut)

Be aware that if you need to overwrite the file, you should include an overwrite parameter:

val fileIn = Path("newFile.txt")
val fileOut = Path("copyNewFile.txt")
fileIn.copyTo(fileOut, overwrite = true)

To copy a whole directory recursively, you can use the copyRecursively() function:

val pathIn = Path("outDir")
val pathOut = Path("newDir")
pathIn.copyToRecursively(pathOut)

Remember that if you need to overwrite folders and files, you should also introduce an overwrite parameter or follow the links (only in Path) with followLinks option:

val pathIn = Path("outDir")
val pathOut = Path("newDir")
pathIn.copyToRecursively(pathOut, overwrite = true, followLinks = true)

Conclusion

We have examined how to put together multiple directories and files into a complex hierarchy. Various methods to traverse file hierarchies were considered, using an example for clarity. Although these methods allow for creating general algorithms to traverse any file hierarchy, we skipped exception checking for simplicity's sake. Don't forget to include it in your programs.

20 learners liked this piece of theory. 4 didn't like it. What about you?
Report a typo