Kotlin Files.copy

The Files class found in JDK provides a utility method that allows copying from an input stream into a file. The copy operation is found on lines 22 and 28. Here is an example program followed by a detailed explanation.

import java.io.Console
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption

private fun Path.isFile() = !Files.isDirectory(this)

private fun Path.exists() = Files.exists(this)

private fun replace(): Boolean {
        val console = console()
        val replace = console.readLine("File already exists! Enter Y to replace => ")
        return replace.toLowerCase() == "y"
}

private fun Path.doCopy(dest : Path) : Boolean {
    return if(isFile()){
        if(dest.exists()){
            if(replace()){
                //We need to pass StandardCopyOption.REPLACE_EXISTING when overwriting a file
                Files.copy(this, dest, StandardCopyOption.REPLACE_EXISTING)
                true
            } else {
                false
            }
        } else {
            Files.copy(this, dest)
            true
        }
    } else {
        false
    }
}

private fun console() : Console {
    val console = System.console()
    return if(console != null){
        console
    } else {
        println("Please run from the terminal")
        System.exit(-1)

        //Return needed for compiler but we never actually reach this statement
        console!!
    }
}

fun main(args : Array<String>){
    when (args.size){
        2 -> {
            val src = Paths.get(args[0])
            val dest = Paths.get(args[1])

            if(src.doCopy(dest)){
                println("Copied ${src.fileName} to ${dest.fileName}")
            }
        }
        else -> {
            println("Usage: src dest")
        }
    }
}

Extension Functions

The example program uses Kotlin’s extension functions to help simplify the code. For example, it seems more natural to call exists() on a Path object as opposed to Files.exists(path). Likewise, it seems more natual to call copy on a Path object rather than Files.copy(src, dest). For this reason, we define a number of extension functions in the program.

The first extension function is found on line 7.

private fun Path.isFile() = !Files.isDirectory(this)

There isn’t a lot of magic here. All we are doing is wrapping a call to Files.isDirectory inside of the extension function. It allows us to call path.isFile() later on.

The next function is equally as brief.

private fun Path.exists() = Files.exists(this)

Once again, we are writing this function so that we can call path.exists() later on in the program.

The next function is replace().

private fun replace(): Boolean {
        val console = console()
        val replace = console.readLine("File already exists! Enter Y to replace => ")
        return replace.toLowerCase() == "y"
}

This function is used by the program to prompt the user if the file already exists. As we will see, we need to pass StandardCopyOption.REPLACE_EXISTING to overwrite a file or it will throw an exception. The function makes a call to a console() function that returns a non-null Console object or exits the program.

private fun console() : Console {
    val console = System.console()
    return if(console != null){
        console
    } else {
        println("Please run from the terminal")
        System.exit(-1)

        //Return needed for compiler but we never actually reach this statement
        console!!
    }
}

The final extension function is doCopy(). This is the function that actually contains the call to Files.copy(), which is the topic of this post.

private fun Path.doCopy(dest : Path) : Boolean {
    return if(isFile()){
        if(dest.exists()){
            if(replace()){
                //We need to pass StandardCopyOption.REPLACE_EXISTING when overwriting a file
                Files.copy(this, dest, StandardCopyOption.REPLACE_EXISTING)
                true
            } else {
                false
            }
        } else {
            Files.copy(this, dest)
            true
        }
    } else {
        false
    }
}

The doCopy function uses our isFile() and exist() extension functions on both the src and dest Path objects. When isFile(), exists() and replace() return true, we make a call to Files.copy, passing in this as our source, dest as our path, and StandardCopyOption.REPLACE_EXISTING. The function will make a copy of the source Path (this) into the dest and overwrite the dest should it already exist.

The alternative case is used when the dest file doesn’t already exist. Since the destination file doesn’t exist, there is no need to prompt the user about replacing it and we do not need to pass any StandardCopyOption to the call to Files.copy(). The program will simply copy one file to the other and the function will return true.

The main() function is the final function in the program.

fun main(args : Array<String>){
    when (args.size){
        2 -> {
            val src = Paths.get(args[0])
            val dest = Paths.get(args[1])

            if(src.doCopy(dest)){
                println("Copied ${src.fileName} to ${dest.fileName}")
            }
        }
        else -> {
            println("Usage: src dest")
        }
    }
}

The main() function gets mention because it uses the previously discussed doCopy() extension function. When the function is true, we tell the user that we copied the files. Otherwise the program exits.

References

https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#copy-java.io.InputStream-java.nio.file.Path-java.nio.file.CopyOption…-
https://kotlinlang.org/docs/reference/extensions.html

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: