Kotlin Files Attributes and List Files/Folders in Directory

JDK8 has a Files class that provides utility methods to list all files and folders in a directory and list attributes of a file.

import java.nio.file.Files
import java.nio.file.Paths
import java.util.stream.Collectors

fun main(args : Array<String>){
    //Create a Path object
    val path = Paths.get(if(args.isEmpty()){
        System.getProperty("user.dir")
    } else {
        args[0]
    })

    //Check if the Path is a directory
    if (Files.isDirectory(path)){
        //List all items in the directory. Note that we are using Java 8 streaming API to group the entries by
        //directory and files
        val fileDirMap = Files.list(path).collect(Collectors.partitioningBy( {it -> Files.isDirectory(it)}))

        println("Directories")
        //Print out all of the directories
        fileDirMap[true]?.forEach { it -> println(it.fileName) }

        println("\nFiles")
        println("%-20s\tRead\tWrite\tExecute".format("Name"))
        //Print out all files and attributes
        fileDirMap[false]?.forEach( {it ->
            println("%-20s\t%-5b\t%-5b\t%b".format(
                    it.fileName,
                    Files.isReadable(it), //Read attribute
                    Files.isWritable(it), //Write attribute
                    Files.isExecutable(it))) //Execute attribute
        })
    } else {
        println("Enter a directory")
    }
}

Output

Directories
target
.idea
src

Files
Name                	Read	Write	Execute
belchers.txt        	true 	true 	false
belchers.burgers    	true 	true 	false
pom.xml             	true 	true 	false
bob.ser             	true 	true 	false
OCJAP.iml           	true 	true 	false
bob.csv             	true 	true 	false
data.burgers        	true 	true 	false

Explanation

The program begins by testing for command line arguments. If a command line argument is available, the command line argument is used for the Path. Otherwise, we use the current working directory. The result is passed to Paths.get() and a Path object is returned.

Line 14 checks if the Path is a directory by using Files.isDirectory(). If true, the program proceeds to line 17 otherwise exits with an error message. Line 17 obtains a map of all directories and files in the path. We use the Files.list() method which returns a Java 8 Stream object. The next part transforms the Stream into a Map<Boolean, List> by using Collectors.partioningBy(). In our case, we are patitioning by true or false.

We pass the it variable (which is a Path object), to Files.isDirectory(). When the result is true, all Paths are grouped with the true key on the resulting map. All false results are grouped into a list and placed into the false key in the resulting map. When the operation is complete, we will have all files and directories sorted.

Line 21 prints out all directories in the folder. In our case, we are simply using forEach() to print out all of the directories. For demonstration purposes, we include additional information with each of the files. Lines 26-32 prints out the file name, read attribute, write attribute, and executable attribute.

The file name is obtained from the fileName property on the Path (line 28). Next we test if the file is readable by using the Files.isReadable() method (line 29. The Files class also has isWritable and isExecutable which work the same as isReadable (lines 30, 31 respectively). All methods return either true or false.

Methods Used

isDirectory(path : Path) : Boolean

Returns true when the Path object points at a directory, otherwise false.

val dir = Files.isDirectory(Paths.get(System.getProperty("user.dir")))

list(dir : Path) : Stream

Returns a Java 8 Stream object of type Path. All of the elements of the stream are lazily populated and contain the entries in a directory.

Files.list(Paths.get(System.getProperty("user.dir"))).forEach{it -> println(it.name) })

isReadable(path : Path) : Boolean

True when the file is readable, otherwise false.

val r = Files.isReadable(Paths.get(System.getProperty("user.dir")))

isWritable(path : Path) : Boolean

True when the file is writable

val w = Files.isWritable(Paths.get(System.getProperty("user.dir")))

isExecutable(path : Path) : Boolean

Tre when the file is an executable.

val e = Files.isExecutable(Paths.get(System.getProperty("user.dir")))

References

https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html

Advertisements

Kotlin Files.exists() and Files.isSameFile()

The Files class provides utility methods that are useful for working with the file system.

import java.io.BufferedWriter
import java.io.FileWriter
import java.nio.file.Files
import java.nio.file.Paths


private val belchers = "belchers.txt"

private fun makeBelcherFile(){
    val names = listOf("Bob", "Linda", "Tina", "Gene", "Louise")
    BufferedWriter(FileWriter(ch9.paths.belchers)).use { writer ->
        names.forEach { name ->
            with(writer){
                write(name)
                newLine()
            }
        }
    }
}

fun main(args : Array<String>){
    //Check if the file exists first, and create it if needed
    if (!Files.exists(Paths.get(belchers))){
        makeBelcherFile()
    }
    val relativeBeclhers = Paths.get(belchers)
    val absoluteBelchers = Paths.get(System.getProperty("user.dir"), belchers)

    //Check if both Paths point to the same file
    println("Using Files.isSameFile() => " + (Files.isSameFile(relativeBeclhers, absoluteBelchers)))
}

Output

Using Files.isSameFile() => true

The program uses Files.exists to see if we have a belchers.txt file on the underlying os. If the method returns false, we call makeBelchersFile() on line 24 to create the file. Lines 26 and 27 create two different Path objects to point at the belchers.txt file.

The relativeBelchers is a Path object created using a relative path to the file. The absoluateBelchers object is created with an aboslute path by combining the current working directory with the name of the file. One line 38, we use the Files.isSameFile and pass both of the Path objects to it. Since both of these Paths point at the same file, it returns True.

Methods

exists(path : Path, varages options : LinkOptions) : Boolean

The exists method is used to test if a file exists or not. We can also pass optional LinkOptions that instructs the method on how to handle symbolic links in the file system. For example, if we don’t want to follow links, then pass NOFOLLOE_LNKS.

isSameFile(path : Path, path2 : Path)

Tests if the both path objects point to the same file. It’s the same as checking if path == path2.

References

https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#isSameFile-java.nio.file.Path-java.nio.file.Path-

Kotlin Compare Paths

The Paths interface overrides equals() and compareTo(), allowing us to compare paths in the file system.

import java.nio.file.Paths

fun main(args : Array<String>){
    //Get two references to the home directory
    val home = Paths.get(System.getProperty("user.home"))
    val otherHome = Paths.get(System.getProperty("user.home"))
    
    //Get the current working directory
    val cwd = Paths.get(System.getProperty("user.dir"))

    println("$home == $otherHome is " + (home == otherHome))
    println("$home == $cwd is " + (home == cwd))
    println("$home < $cwd is " + (home < cwd))
    println("$cwd < $home is " + (cwd < home))
    println("$home >= $otherHome is " + (home >= otherHome))
}

Output

/Users/stonesoup == /Users/stonesoup is true
/Users/stonesoup == /Users/stonesoup/IdeaProjects/OCJAP is false
/Users/stonesoup < /Users/stonesoup/IdeaProjects/OCJAP is true
/Users/stonesoup/IdeaProjects/OCJAP < /Users/stonesoup is false
/Users/stonesoup >= /Users/stonesoup is true

compareTo(other : Path) : Int

The javadoc states that two paths are compared lexicographically as defined by the file system provider. The default provider uses a platform specific means of comparing file paths.

equals(other : Object) : Boolean

Assuming the other is a Path object and is not associated with a different file system, the paths are equal if they are the same path. Keep in mind that case sensitivity may apply. Mac OS X is case insensitive so “belchers.txt” is the same as “Belchers.TXT”, but on Linux, this would be false.

References

https://docs.oracle.com/javase/8/docs/api/java/nio/file/Path.htm

Kotlin Path Interface

The Path interface is provided to Kotlin by the java.nio.file.Paths package. It represents Paths on the underlying file system and provides a number of useful utility methods for working with paths. Here is an example program followed by output and an explanation.

import java.io.BufferedWriter
import java.io.FileWriter
import java.nio.file.Paths

val belchers = "belchers.txt"

fun makeBelcherFile(){
    val names = listOf("Bob", "Linda", "Tina", "Gene", "Louise")
    BufferedWriter(FileWriter(belchers)).use { writer ->
        names.forEach { name ->
            with(writer){
                write(name)
                newLine()
            }
        }
    }
}

fun main(args : Array<String>){
    //Just make a file on the file system for demonstration purposes
    makeBelcherFile()

    //Get a reference to our example path on the disk.
    //In this case, we are using Paths.get() to get a reference to the current working directory
    //and then using the resolve() method to add the belchers.txt file to the path
    val belcherPath = Paths.get(System.getProperty("user.dir")).resolve(belchers)

    val template = "\t%-30s => %s"

    with(belcherPath){
        println("File Information")
        //The fileName property returns the name of the file
        println(template.format("File Name", fileName))

        //The root property return the the root folder of the path
        println(template.format("File Path Root", root))

        //The parent property returns the parent folder of the file
        println(template.format("File Path Parent", parent))

        //The nameCount returns how many items are in the path
        println(template.format("Name Count", nameCount))

        //The subpath() method returns a portion of the path
        println(template.format("Subpath (0, 1)", subpath(0, 1)))

        //The normalize method returns items such as . or .. from the path
        println(template.format("Normalizing", normalize()))

        //True if this is an absolute path otherwise false
        println(template.format("Is Absolute Path?", isAbsolute))

        //Convert to an absolute path if needed
        println(template.format("Absolute Path", toAbsolutePath()))

        //Check if the path starts with a path. In this example, we are using the home folder
        println(template.format("Starts with ${System.getProperty("user.home")}?", startsWith(System.getProperty("user.home"))))
        println()

        println("Elements of the Path")
        //We can print each portion of the path individually also!
        forEach { it -> println("\tPortion => $it") }
    }
}

Here is the output when run on my machine.

File Information
	File Name                      => belchers.txt
	File Path Root                 => /
	File Path Parent               => /Users/stonesoup/IdeaProjects/OCJAP
	Name Count                     => 5
	Subpath (0, 1)                 => Users
	Normalizing                    => /Users/stonesoup/IdeaProjects/OCJAP/belchers.txt
	Is Absolute Path?              => true
	Absolute Path                  => /Users/stonesoup/IdeaProjects/OCJAP/belchers.txt
	Starts with /Users/stonesoup?  => true

Elements of the Path
	Portion => Users
	Portion => stonesoup
	Portion => IdeaProjects
	Portion => OCJAP
	Portion => belchers.txt

Explanation

The program writes out a basic text file to the file system for demonstration purposes. We are going to focus on the main function. Our first task is to get a Path object that points to our belchers.txt file. We use the Paths.get() factory method and path in a path on the file system. In our example, we use the current working directory by using the System property “user.dir”.

We could have also added the belchers.txt to the end of the current working directory. However, I wanted to demonstrate the resolve method that combines two paths into a single path. So we chain the resolve method to the returned Path object and add belchers.txt. The returned Path object points to the path of belchers.txt on the file system.

The next part of the program demonstrates commonly used methods found on the Path interface. Line 33 prints the name of the file by using the fileName property. Next we print out the root of the path by using the root property (line 36). When we want to know the parent of a path, we can use the parent property (line 39).

The Path interface has a nameCount property (line 42) that returns the number of items in a path. So if a path is /Users/stonesoup/IdeaProjects/OCJAP/belchers.txt, nameCount returns 5, one for each item between each slash (/) character. The nameCount is useful when working with the Subpath function (line 45), which accepts a start index (inclusive) and an end index (exclusive) and returns a Path object based on the indexes.

Sometimes paths are abnormal paths and may have “.” or “..” characters in the path. When we want to remove such characters, we use the normalize() function (line 48) which strips out abnormal characters from the path. Depending on the work we may be doing, we may want to test if the Path is a relative path or an abosulte path. The Path interface has an isAbsolute property (line 51) for such purposes. It returns true if the path is an absolute path otherwise false.

Should we wish to convert a relative path into an absolute path, we only need to call the toAbsolutePath() function (line 54) and we will get an absolute path. We can also check if a path starts with a certain path. In our example, line 57, we check if our path starts with the users home directory (user.home). It returns true or false based on the outcome.

Path supports the forEach() function. Line 62 shows an example of how we can iterate through each part of the Path. The it variable holds each portion of the path and the program prints each part of the path.

Common Methods

We spoke about each method as it relates the program above. Here are each of the commonly used methods broken down.

Paths.get(first : String, varages more : String) : Path

The get() converts a String (or URI in the overloaded version) into a Path object. When we use use the varags part, the Path will use the OS name seperator. So Unix paths will have a forward slash, while Windows ones will have a backslash.

val home = Paths.get(System.getProperty("user.home"))

parent : Path

The parent property returns a Path object that points to the parent of the current Path object.

val parent = home.parent

nameCount : Int

The nameCount returns the number of items in the path.

val count = home.nameCount

subPath(beginIndex : Int, endIndex : Int) : Path

The subPath method is used to return a portion of the path object. The beginIndex is inclusive while the endIndex is exclusive.

val part = home.subPath(0, 2)

normalize() : Path

The normalize() method returns a Path object without unneeded characters.

val norm = home.normalize()

resolve(other : Path) : Path, resolve(other : String): Path

Returns a Path object that is a combined path between the current path and the other parameter.

val belchers = home.resolve("belchers.txt")

isAbsolute : Boolean

True if the Path is an absolute path otherwise it’s false.

val absolute = home.isAbsolute

startsWith(path : String) : Boolean, startsWith(path : Path) : Boolean

True if the current path starts with the supplied path argument.

val hasRoot = home.startsWith("/")

toAbsolutePath() : Path

Returns a Path object that is the aboslute path of the current Path.

val abs = home.toAbsolutePath()

References

https://docs.oracle.com/javase/8/docs/api/java/nio/file/Path.html