Provide visit extension functions for java.nio.file.Path #KT-52910
This commit is contained in:
committed by
Space
parent
e7b37b3497
commit
78666e3ecb
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.io.path
|
||||
|
||||
import java.io.IOException
|
||||
import java.nio.file.FileVisitResult
|
||||
import java.nio.file.FileVisitor
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.SimpleFileVisitor
|
||||
import java.nio.file.attribute.BasicFileAttributes
|
||||
|
||||
/**
|
||||
* The builder to provide implementation of the file visitor that [fileVisitor] builds.
|
||||
*/
|
||||
@ExperimentalPathApi
|
||||
@SinceKotlin("1.7")
|
||||
public sealed interface FileVisitorBuilder {
|
||||
/**
|
||||
* Overrides the corresponding function of the built file visitor with the provided [function].
|
||||
*
|
||||
* By default, [FileVisitor.preVisitDirectory] of the built file visitor returns [FileVisitResult.CONTINUE].
|
||||
*/
|
||||
public fun onPreVisitDirectory(function: (directory: Path, attributes: BasicFileAttributes) -> FileVisitResult): Unit
|
||||
|
||||
/**
|
||||
* Overrides the corresponding function of the built file visitor with the provided [function].
|
||||
*
|
||||
* By default, [FileVisitor.visitFile] of the built file visitor returns [FileVisitResult.CONTINUE].
|
||||
*/
|
||||
public fun onVisitFile(function: (file: Path, attributes: BasicFileAttributes) -> FileVisitResult): Unit
|
||||
|
||||
/**
|
||||
* Overrides the corresponding function of the built file visitor with the provided [function].
|
||||
*
|
||||
* By default, [FileVisitor.visitFileFailed] of the built file visitor re-throws the I/O exception
|
||||
* that prevented the file from being visited.
|
||||
*/
|
||||
public fun onVisitFileFailed(function: (file: Path, exception: IOException) -> FileVisitResult): Unit
|
||||
|
||||
/**
|
||||
* Overrides the corresponding function of the built file visitor with the provided [function].
|
||||
*
|
||||
* By default, if the directory iteration completes without an I/O exception,
|
||||
* [FileVisitor.postVisitDirectory] of the built file visitor returns [FileVisitResult.CONTINUE];
|
||||
* otherwise it re-throws the I/O exception that caused the iteration of the directory to terminate prematurely.
|
||||
*/
|
||||
public fun onPostVisitDirectory(function: (directory: Path, exception: IOException?) -> FileVisitResult): Unit
|
||||
}
|
||||
|
||||
|
||||
@ExperimentalPathApi
|
||||
internal class FileVisitorBuilderImpl : FileVisitorBuilder {
|
||||
private var onPreVisitDirectory: ((Path, BasicFileAttributes) -> FileVisitResult)? = null
|
||||
private var onVisitFile: ((Path, BasicFileAttributes) -> FileVisitResult)? = null
|
||||
private var onVisitFileFailed: ((Path, IOException) -> FileVisitResult)? = null
|
||||
private var onPostVisitDirectory: ((Path, IOException?) -> FileVisitResult)? = null
|
||||
private var isBuilt: Boolean = false
|
||||
|
||||
override fun onPreVisitDirectory(function: (directory: Path, attributes: BasicFileAttributes) -> FileVisitResult): Unit {
|
||||
checkIsNotBuilt()
|
||||
checkNotDefined(onPreVisitDirectory, "onPreVisitDirectory")
|
||||
onPreVisitDirectory = function
|
||||
}
|
||||
|
||||
override fun onVisitFile(function: (file: Path, attributes: BasicFileAttributes) -> FileVisitResult): Unit {
|
||||
checkIsNotBuilt()
|
||||
checkNotDefined(onVisitFile, "onVisitFile")
|
||||
onVisitFile = function
|
||||
}
|
||||
|
||||
override fun onVisitFileFailed(function: (file: Path, exception: IOException) -> FileVisitResult): Unit {
|
||||
checkIsNotBuilt()
|
||||
checkNotDefined(onVisitFileFailed, "onVisitFileFailed")
|
||||
onVisitFileFailed = function
|
||||
}
|
||||
|
||||
override fun onPostVisitDirectory(function: (directory: Path, exception: IOException?) -> FileVisitResult): Unit {
|
||||
checkIsNotBuilt()
|
||||
checkNotDefined(onPostVisitDirectory, "onPostVisitDirectory")
|
||||
onPostVisitDirectory = function
|
||||
}
|
||||
|
||||
fun build(): FileVisitor<Path> {
|
||||
checkIsNotBuilt()
|
||||
isBuilt = true
|
||||
return FileVisitorImpl(onPreVisitDirectory, onVisitFile, onVisitFileFailed, onPostVisitDirectory)
|
||||
}
|
||||
|
||||
private fun checkIsNotBuilt() {
|
||||
if (isBuilt) throw IllegalStateException("This builder was already built")
|
||||
}
|
||||
|
||||
private fun checkNotDefined(function: Any?, name: String) {
|
||||
if (function != null) throw IllegalStateException("$name was already defined")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class FileVisitorImpl(
|
||||
private val onPreVisitDirectory: ((Path, BasicFileAttributes) -> FileVisitResult)?,
|
||||
private val onVisitFile: ((Path, BasicFileAttributes) -> FileVisitResult)?,
|
||||
private val onVisitFileFailed: ((Path, IOException) -> FileVisitResult)?,
|
||||
private val onPostVisitDirectory: ((Path, IOException?) -> FileVisitResult)?,
|
||||
) : SimpleFileVisitor<Path>() {
|
||||
override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult =
|
||||
this.onPreVisitDirectory?.invoke(dir, attrs) ?: super.preVisitDirectory(dir, attrs)
|
||||
|
||||
override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult =
|
||||
this.onVisitFile?.invoke(file, attrs) ?: super.visitFile(file, attrs)
|
||||
|
||||
override fun visitFileFailed(file: Path, exc: IOException): FileVisitResult =
|
||||
this.onVisitFileFailed?.invoke(file, exc) ?: super.visitFileFailed(file, exc)
|
||||
|
||||
override fun postVisitDirectory(dir: Path, exc: IOException?): FileVisitResult =
|
||||
this.onPostVisitDirectory?.invoke(dir, exc) ?: super.postVisitDirectory(dir, exc)
|
||||
}
|
||||
@@ -15,6 +15,8 @@ import java.nio.file.*
|
||||
import java.nio.file.FileAlreadyExistsException
|
||||
import java.nio.file.NoSuchFileException
|
||||
import java.nio.file.attribute.*
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
@@ -1015,3 +1017,115 @@ public inline fun URI.toPath(): Path =
|
||||
@ExperimentalPathApi
|
||||
@SinceKotlin("1.7")
|
||||
public fun Path.walk(vararg options: PathWalkOption): Sequence<Path> = PathTreeWalk(this, options)
|
||||
|
||||
/**
|
||||
* Visits this directory and all its content with the specified [visitor].
|
||||
*
|
||||
* The traversal is in depth-first order and starts at this directory. The specified [visitor] is invoked on each file encountered.
|
||||
*
|
||||
* @param visitor the [FileVisitor] that receives callbacks.
|
||||
* @param maxDepth the maximum depth to traverse. By default, there is no limit.
|
||||
* @param followLinks specifies whether to follow symbolic links, `false` by default.
|
||||
*
|
||||
* @see Files.walkFileTree
|
||||
*/
|
||||
@ExperimentalPathApi
|
||||
@SinceKotlin("1.7")
|
||||
public fun Path.visitFileTree(visitor: FileVisitor<Path>, maxDepth: Int = Int.MAX_VALUE, followLinks: Boolean = false): Unit {
|
||||
val options = if (followLinks) setOf(FileVisitOption.FOLLOW_LINKS) else setOf()
|
||||
Files.walkFileTree(this, options, maxDepth, visitor)
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits this directory and all its content with the [FileVisitor] defined in [builderAction].
|
||||
*
|
||||
* This function works the same as [Path.visitFileTree]. It is introduced to streamline
|
||||
* the cases when a [FileVisitor] is created only to be immediately used for a file tree traversal.
|
||||
* The trailing lambda [builderAction] is passed to [fileVisitor] to get the file visitor.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ``` kotlin
|
||||
* projectDirectory.visitFileTree {
|
||||
* onPreVisitDirectory { directory, _ ->
|
||||
* if (directory.name == "build") {
|
||||
* directory.toFile().deleteRecursively()
|
||||
* FileVisitResult.SKIP_SUBTREE
|
||||
* } else {
|
||||
* FileVisitResult.CONTINUE
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* onVisitFile { file, _ ->
|
||||
* if (file.extension == "class") {
|
||||
* file.deleteExisting()
|
||||
* }
|
||||
* FileVisitResult.CONTINUE
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param maxDepth the maximum depth to traverse. By default, there is no limit.
|
||||
* @param followLinks specifies whether to follow symbolic links, `false` by default.
|
||||
* @param builderAction the function that defines [FileVisitor].
|
||||
*
|
||||
* @see Path.visitFileTree
|
||||
* @see fileVisitor
|
||||
*/
|
||||
@ExperimentalPathApi
|
||||
@SinceKotlin("1.7")
|
||||
public fun Path.visitFileTree(
|
||||
maxDepth: Int = Int.MAX_VALUE,
|
||||
followLinks: Boolean = false,
|
||||
builderAction: FileVisitorBuilder.() -> Unit
|
||||
): Unit {
|
||||
contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
|
||||
visitFileTree(fileVisitor(builderAction), maxDepth, followLinks)
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a [FileVisitor] whose implementation is defined in [builderAction].
|
||||
*
|
||||
* By default, the returned file visitor visits all files and re-throws I/O errors, that is:
|
||||
* * [FileVisitor.preVisitDirectory] returns [FileVisitResult.CONTINUE].
|
||||
* * [FileVisitor.visitFile] returns [FileVisitResult.CONTINUE].
|
||||
* * [FileVisitor.visitFileFailed] re-throws the I/O exception that prevented the file from being visited.
|
||||
* * [FileVisitor.postVisitDirectory] returns [FileVisitResult.CONTINUE] if the directory iteration completes without an I/O exception;
|
||||
* otherwise it re-throws the I/O exception that caused the iteration of the directory to terminate prematurely.
|
||||
*
|
||||
* To override a function provide its implementation to the corresponding
|
||||
* function of the [FileVisitorBuilder] that was passed as a receiver to [builderAction].
|
||||
* Note that each function can be overridden only once.
|
||||
* Repeated override of a function throws [IllegalStateException].
|
||||
*
|
||||
* The builder is valid only inside [builderAction] function.
|
||||
* Using it outside the function throws [IllegalStateException].
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ``` kotlin
|
||||
* val cleanVisitor = fileVisitor {
|
||||
* onPreVisitDirectory { directory, _ ->
|
||||
* if (directory.name == "build") {
|
||||
* directory.toFile().deleteRecursively()
|
||||
* FileVisitResult.SKIP_SUBTREE
|
||||
* } else {
|
||||
* FileVisitResult.CONTINUE
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* onVisitFile { file, _ ->
|
||||
* if (file.extension == "class") {
|
||||
* file.deleteExisting()
|
||||
* }
|
||||
* FileVisitResult.CONTINUE
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@ExperimentalPathApi
|
||||
@SinceKotlin("1.7")
|
||||
public fun fileVisitor(builderAction: FileVisitorBuilder.() -> Unit): FileVisitor<Path> {
|
||||
contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
|
||||
return FileVisitorBuilderImpl().apply(builderAction).build()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user