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.FileAlreadyExistsException
|
||||||
import java.nio.file.NoSuchFileException
|
import java.nio.file.NoSuchFileException
|
||||||
import java.nio.file.attribute.*
|
import java.nio.file.attribute.*
|
||||||
|
import kotlin.contracts.InvocationKind
|
||||||
|
import kotlin.contracts.contract
|
||||||
import kotlin.jvm.Throws
|
import kotlin.jvm.Throws
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1015,3 +1017,115 @@ public inline fun URI.toPath(): Path =
|
|||||||
@ExperimentalPathApi
|
@ExperimentalPathApi
|
||||||
@SinceKotlin("1.7")
|
@SinceKotlin("1.7")
|
||||||
public fun Path.walk(vararg options: PathWalkOption): Sequence<Path> = PathTreeWalk(this, options)
|
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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,335 @@
|
|||||||
|
/*
|
||||||
|
* 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.jdk7.test
|
||||||
|
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.nio.file.*
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipFile
|
||||||
|
import java.util.zip.ZipOutputStream
|
||||||
|
import kotlin.io.path.*
|
||||||
|
import kotlin.jdk7.test.PathTreeWalkTest.Companion.createTestFiles
|
||||||
|
import kotlin.jdk7.test.PathTreeWalkTest.Companion.referenceFilenames
|
||||||
|
import kotlin.jdk7.test.PathTreeWalkTest.Companion.referenceFilesOnly
|
||||||
|
import kotlin.jdk7.test.PathTreeWalkTest.Companion.testVisitedFiles
|
||||||
|
import kotlin.jdk7.test.PathTreeWalkTest.Companion.tryCreateSymbolicLinkTo
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
class FileVisitorBuilderTest : AbstractPathTest() {
|
||||||
|
@Test
|
||||||
|
fun visitOnce() {
|
||||||
|
val basedir = createTestFiles().cleanupRecursively()
|
||||||
|
|
||||||
|
val preVisit = hashSetOf<Path>()
|
||||||
|
val postVisit = hashSetOf<Path>()
|
||||||
|
val files = hashSetOf<Path>()
|
||||||
|
|
||||||
|
val visitor = fileVisitor {
|
||||||
|
onPreVisitDirectory { directory, _ ->
|
||||||
|
assertFalse(directory in preVisit)
|
||||||
|
if (directory == basedir) {
|
||||||
|
assertTrue(preVisit.isEmpty())
|
||||||
|
} else {
|
||||||
|
assertTrue(directory.parent in preVisit)
|
||||||
|
}
|
||||||
|
preVisit.add(directory)
|
||||||
|
|
||||||
|
FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisitFile { file, _ ->
|
||||||
|
assertTrue(file.parent in preVisit)
|
||||||
|
assertFalse(file.parent in postVisit)
|
||||||
|
files.add(file)
|
||||||
|
|
||||||
|
FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
|
||||||
|
onPostVisitDirectory { directory, exception ->
|
||||||
|
assertNull(exception)
|
||||||
|
assertTrue(directory in preVisit)
|
||||||
|
assertFalse(directory in postVisit)
|
||||||
|
if (directory != basedir) {
|
||||||
|
assertFalse(directory.parent in postVisit)
|
||||||
|
}
|
||||||
|
postVisit.add(directory)
|
||||||
|
|
||||||
|
FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
basedir.visitFileTree(visitor)
|
||||||
|
|
||||||
|
assertEquals(preVisit, postVisit)
|
||||||
|
val referenceDirectoryNames = listOf("", "1", "1/2", "1/3", "6", "8")
|
||||||
|
testVisitedFiles(referenceDirectoryNames, preVisit.asSequence(), basedir)
|
||||||
|
testVisitedFiles(referenceFilesOnly, files.asSequence(), basedir)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun overrideOnce() {
|
||||||
|
assertFailsWith<IllegalStateException> {
|
||||||
|
fileVisitor {
|
||||||
|
onVisitFile { _, _ -> FileVisitResult.CONTINUE }
|
||||||
|
onVisitFile { _, _ -> FileVisitResult.CONTINUE }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun overrideInAlreadyBuilt() {
|
||||||
|
val builder: FileVisitorBuilder
|
||||||
|
fileVisitor { builder = this }
|
||||||
|
assertFailsWith<IllegalStateException> {
|
||||||
|
builder.onVisitFile { _, _ -> FileVisitResult.CONTINUE }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun restrictedRead() {
|
||||||
|
val basedir = createTestFiles().cleanupRecursively()
|
||||||
|
val restrictedDir = basedir.resolve("1/3")
|
||||||
|
|
||||||
|
withRestrictedRead(restrictedDir) {
|
||||||
|
assertFailsWith<AccessDeniedException> {
|
||||||
|
val visitor = fileVisitor { }
|
||||||
|
basedir.visitFileTree(visitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
var didFail = false
|
||||||
|
|
||||||
|
basedir.visitFileTree {
|
||||||
|
onVisitFileFailed { file, exception ->
|
||||||
|
assertEquals(restrictedDir, file)
|
||||||
|
assertIs<AccessDeniedException>(exception)
|
||||||
|
|
||||||
|
didFail = true
|
||||||
|
|
||||||
|
FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(didFail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun skipDirectory() {
|
||||||
|
val basedir = createTestFiles().cleanupRecursively()
|
||||||
|
val dirToSkip = basedir.resolve("1/3")
|
||||||
|
val visitedFiles = mutableListOf<Path>()
|
||||||
|
|
||||||
|
basedir.visitFileTree {
|
||||||
|
onPreVisitDirectory { directory, _ ->
|
||||||
|
if (directory == dirToSkip)
|
||||||
|
FileVisitResult.SKIP_SUBTREE
|
||||||
|
else
|
||||||
|
FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisitFile { file, _ ->
|
||||||
|
assertTrue(file.parent != dirToSkip)
|
||||||
|
|
||||||
|
visitedFiles.add(file)
|
||||||
|
|
||||||
|
FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testVisitedFiles(listOf("7.txt", "8/9.txt"), visitedFiles.asSequence(), basedir)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun maxDepth() {
|
||||||
|
val basedir = createTestFiles().cleanupRecursively()
|
||||||
|
|
||||||
|
val visitor = fileVisitor {
|
||||||
|
onVisitFile { file, _ ->
|
||||||
|
assertNotEquals("1/3/5.txt", file.relativeToOrSelf(basedir).invariantSeparatorsPathString)
|
||||||
|
assertNotEquals("1/3/6.txt", file.relativeToOrSelf(basedir).invariantSeparatorsPathString)
|
||||||
|
FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
basedir.visitFileTree(visitor, maxDepth = 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun followLinks() {
|
||||||
|
val basedir = createTestFiles().cleanupRecursively()
|
||||||
|
val original = basedir.resolve("8")
|
||||||
|
basedir.resolve("1/3/link").tryCreateSymbolicLinkTo(original) ?: return
|
||||||
|
|
||||||
|
// directory "8" contains "9.txt" file
|
||||||
|
var didFollowLinks = false
|
||||||
|
val visitor = fileVisitor {
|
||||||
|
onVisitFile { file, _ ->
|
||||||
|
if (file.relativeToOrSelf(basedir).invariantSeparatorsPathString == "1/3/link/9.txt") {
|
||||||
|
didFollowLinks = true
|
||||||
|
}
|
||||||
|
FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
basedir.visitFileTree(visitor)
|
||||||
|
assertFalse(didFollowLinks)
|
||||||
|
|
||||||
|
basedir.visitFileTree(visitor, followLinks = true)
|
||||||
|
assertTrue(didFollowLinks)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun copyRecursively() {
|
||||||
|
val srcRoot = createTestFiles().cleanupRecursively()
|
||||||
|
val dstRoot = createTempDirectory().cleanupRecursively()
|
||||||
|
|
||||||
|
srcRoot.resolve("1/2/.dir").createDirectory()
|
||||||
|
srcRoot.resolve("1/3/.file").createFile()
|
||||||
|
|
||||||
|
srcRoot.visitFileTree {
|
||||||
|
onPreVisitDirectory { directory, _ ->
|
||||||
|
if (directory.name.startsWith(".")) {
|
||||||
|
FileVisitResult.SKIP_SUBTREE
|
||||||
|
} else {
|
||||||
|
if (directory != srcRoot) {
|
||||||
|
val dst = dstRoot.resolve(directory.relativeTo(srcRoot))
|
||||||
|
directory.copyTo(dst, StandardCopyOption.COPY_ATTRIBUTES)
|
||||||
|
}
|
||||||
|
FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisitFile { file, _ ->
|
||||||
|
if (!file.name.startsWith(".")) {
|
||||||
|
val dst = dstRoot.resolve(file.relativeTo(srcRoot))
|
||||||
|
file.copyTo(dst, StandardCopyOption.COPY_ATTRIBUTES)
|
||||||
|
}
|
||||||
|
FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val dstWalk = dstRoot.walk(PathWalkOption.INCLUDE_DIRECTORIES)
|
||||||
|
testVisitedFiles(referenceFilenames + listOf(""), dstWalk, dstRoot)
|
||||||
|
val srcWalk = srcRoot.walk(PathWalkOption.INCLUDE_DIRECTORIES)
|
||||||
|
testVisitedFiles(referenceFilenames + listOf("", "1/2/.dir", "1/3/.file"), srcWalk, srcRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deleteRecursively() {
|
||||||
|
val basedir = createTestFiles()
|
||||||
|
|
||||||
|
basedir.visitFileTree {
|
||||||
|
onVisitFile { file, _ ->
|
||||||
|
file.deleteExisting()
|
||||||
|
FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
|
||||||
|
onPostVisitDirectory { directory, _ ->
|
||||||
|
directory.deleteExisting()
|
||||||
|
FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertFalse(basedir.exists())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteWith(excludePredicate: (Path) -> Boolean) = fileVisitor {
|
||||||
|
onPreVisitDirectory { directory, _ ->
|
||||||
|
if (excludePredicate(directory)) {
|
||||||
|
FileVisitResult.SKIP_SUBTREE
|
||||||
|
} else {
|
||||||
|
FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisitFile { file, _ ->
|
||||||
|
if (!excludePredicate(file)) {
|
||||||
|
file.deleteExisting()
|
||||||
|
}
|
||||||
|
FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
|
||||||
|
onPostVisitDirectory { directory, _ ->
|
||||||
|
val shouldDelete = directory.useDirectoryEntries { it.none() }
|
||||||
|
if (shouldDelete) {
|
||||||
|
directory.deleteExisting()
|
||||||
|
}
|
||||||
|
FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deleteWithPredicate() {
|
||||||
|
val basedir = createTestFiles().cleanupRecursively()
|
||||||
|
basedir.resolve("image1.png").createFile()
|
||||||
|
basedir.resolve("1/3/image2.png").createFile()
|
||||||
|
|
||||||
|
val visitor = deleteWith { path ->
|
||||||
|
when {
|
||||||
|
path.name == "8" -> true
|
||||||
|
path.extension == "png" -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
basedir.visitFileTree(visitor)
|
||||||
|
|
||||||
|
val expected = listOf("", "1", "1/3", "1/3/image2.png", "8", "8/9.txt", "image1.png")
|
||||||
|
testVisitedFiles(expected, basedir.walk(PathWalkOption.INCLUDE_DIRECTORIES), basedir)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun zipify(rootPath: Path, zipOutputStream: ZipOutputStream): FileVisitor<Path> = fileVisitor {
|
||||||
|
onPreVisitDirectory { directory, _ ->
|
||||||
|
if (directory != rootPath) {
|
||||||
|
val entry = ZipEntry(directory.relativeTo(rootPath).toString())
|
||||||
|
|
||||||
|
zipOutputStream.putNextEntry(entry)
|
||||||
|
zipOutputStream.closeEntry()
|
||||||
|
}
|
||||||
|
FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisitFile { file, attributes ->
|
||||||
|
val entry = ZipEntry(file.relativeTo(rootPath).toString())
|
||||||
|
|
||||||
|
entry.size = attributes.size()
|
||||||
|
zipOutputStream.putNextEntry(entry)
|
||||||
|
Files.copy(file, zipOutputStream)
|
||||||
|
zipOutputStream.closeEntry()
|
||||||
|
|
||||||
|
FileVisitResult.CONTINUE
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisitFileFailed { _, exception ->
|
||||||
|
zipOutputStream.close()
|
||||||
|
|
||||||
|
throw exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun archive() {
|
||||||
|
val basedir = createTestFiles().cleanupRecursively()
|
||||||
|
|
||||||
|
val ninthFile = Path("8/9.txt")
|
||||||
|
basedir.resolve(ninthFile).writeText("ninth")
|
||||||
|
|
||||||
|
val zipFile = createTempFile("testFiles", ".zip").toFile()
|
||||||
|
ZipOutputStream(FileOutputStream(zipFile)).use { zipOutputStream ->
|
||||||
|
val visitor = zipify(basedir, zipOutputStream)
|
||||||
|
basedir.visitFileTree(visitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
ZipFile(zipFile).use { zip ->
|
||||||
|
val entriesPath = zip.entries().toList().map { Path(it.name).invariantSeparatorsPathString }
|
||||||
|
assertEquals(referenceFilenames.sorted(), entriesPath.sorted())
|
||||||
|
|
||||||
|
val ninthEntry = zip.getEntry(ninthFile.pathString)
|
||||||
|
zip.getInputStream(ninthEntry).bufferedReader().use { reader ->
|
||||||
|
assertEquals("ninth", reader.readText())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -1,6 +1,13 @@
|
|||||||
public abstract interface annotation class kotlin/io/path/ExperimentalPathApi : java/lang/annotation/Annotation {
|
public abstract interface annotation class kotlin/io/path/ExperimentalPathApi : java/lang/annotation/Annotation {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract interface class kotlin/io/path/FileVisitorBuilder {
|
||||||
|
public abstract fun onPostVisitDirectory (Lkotlin/jvm/functions/Function2;)V
|
||||||
|
public abstract fun onPreVisitDirectory (Lkotlin/jvm/functions/Function2;)V
|
||||||
|
public abstract fun onVisitFile (Lkotlin/jvm/functions/Function2;)V
|
||||||
|
public abstract fun onVisitFileFailed (Lkotlin/jvm/functions/Function2;)V
|
||||||
|
}
|
||||||
|
|
||||||
public final class kotlin/io/path/PathWalkOption : java/lang/Enum {
|
public final class kotlin/io/path/PathWalkOption : java/lang/Enum {
|
||||||
public static final field BREADTH_FIRST Lkotlin/io/path/PathWalkOption;
|
public static final field BREADTH_FIRST Lkotlin/io/path/PathWalkOption;
|
||||||
public static final field FOLLOW_LINKS Lkotlin/io/path/PathWalkOption;
|
public static final field FOLLOW_LINKS Lkotlin/io/path/PathWalkOption;
|
||||||
@@ -17,6 +24,7 @@ public final class kotlin/io/path/PathsKt {
|
|||||||
public static final fun createTempFile (Ljava/nio/file/Path;Ljava/lang/String;Ljava/lang/String;[Ljava/nio/file/attribute/FileAttribute;)Ljava/nio/file/Path;
|
public static final fun createTempFile (Ljava/nio/file/Path;Ljava/lang/String;Ljava/lang/String;[Ljava/nio/file/attribute/FileAttribute;)Ljava/nio/file/Path;
|
||||||
public static synthetic fun createTempFile$default (Ljava/nio/file/Path;Ljava/lang/String;Ljava/lang/String;[Ljava/nio/file/attribute/FileAttribute;ILjava/lang/Object;)Ljava/nio/file/Path;
|
public static synthetic fun createTempFile$default (Ljava/nio/file/Path;Ljava/lang/String;Ljava/lang/String;[Ljava/nio/file/attribute/FileAttribute;ILjava/lang/Object;)Ljava/nio/file/Path;
|
||||||
public static final fun fileAttributeViewNotAvailable (Ljava/nio/file/Path;Ljava/lang/Class;)Ljava/lang/Void;
|
public static final fun fileAttributeViewNotAvailable (Ljava/nio/file/Path;Ljava/lang/Class;)Ljava/lang/Void;
|
||||||
|
public static final fun fileVisitor (Lkotlin/jvm/functions/Function1;)Ljava/nio/file/FileVisitor;
|
||||||
public static final fun getExtension (Ljava/nio/file/Path;)Ljava/lang/String;
|
public static final fun getExtension (Ljava/nio/file/Path;)Ljava/lang/String;
|
||||||
public static final fun getInvariantSeparatorsPathString (Ljava/nio/file/Path;)Ljava/lang/String;
|
public static final fun getInvariantSeparatorsPathString (Ljava/nio/file/Path;)Ljava/lang/String;
|
||||||
public static final fun getName (Ljava/nio/file/Path;)Ljava/lang/String;
|
public static final fun getName (Ljava/nio/file/Path;)Ljava/lang/String;
|
||||||
@@ -28,6 +36,10 @@ public final class kotlin/io/path/PathsKt {
|
|||||||
public static final fun relativeTo (Ljava/nio/file/Path;Ljava/nio/file/Path;)Ljava/nio/file/Path;
|
public static final fun relativeTo (Ljava/nio/file/Path;Ljava/nio/file/Path;)Ljava/nio/file/Path;
|
||||||
public static final fun relativeToOrNull (Ljava/nio/file/Path;Ljava/nio/file/Path;)Ljava/nio/file/Path;
|
public static final fun relativeToOrNull (Ljava/nio/file/Path;Ljava/nio/file/Path;)Ljava/nio/file/Path;
|
||||||
public static final fun relativeToOrSelf (Ljava/nio/file/Path;Ljava/nio/file/Path;)Ljava/nio/file/Path;
|
public static final fun relativeToOrSelf (Ljava/nio/file/Path;Ljava/nio/file/Path;)Ljava/nio/file/Path;
|
||||||
|
public static final fun visitFileTree (Ljava/nio/file/Path;IZLkotlin/jvm/functions/Function1;)V
|
||||||
|
public static final fun visitFileTree (Ljava/nio/file/Path;Ljava/nio/file/FileVisitor;IZ)V
|
||||||
|
public static synthetic fun visitFileTree$default (Ljava/nio/file/Path;IZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
|
||||||
|
public static synthetic fun visitFileTree$default (Ljava/nio/file/Path;Ljava/nio/file/FileVisitor;IZILjava/lang/Object;)V
|
||||||
public static final fun walk (Ljava/nio/file/Path;[Lkotlin/io/path/PathWalkOption;)Lkotlin/sequences/Sequence;
|
public static final fun walk (Ljava/nio/file/Path;[Lkotlin/io/path/PathWalkOption;)Lkotlin/sequences/Sequence;
|
||||||
public static final fun writeText (Ljava/nio/file/Path;Ljava/lang/CharSequence;Ljava/nio/charset/Charset;[Ljava/nio/file/OpenOption;)V
|
public static final fun writeText (Ljava/nio/file/Path;Ljava/lang/CharSequence;Ljava/nio/charset/Charset;[Ljava/nio/file/OpenOption;)V
|
||||||
public static synthetic fun writeText$default (Ljava/nio/file/Path;Ljava/lang/CharSequence;Ljava/nio/charset/Charset;[Ljava/nio/file/OpenOption;ILjava/lang/Object;)V
|
public static synthetic fun writeText$default (Ljava/nio/file/Path;Ljava/lang/CharSequence;Ljava/nio/charset/Charset;[Ljava/nio/file/OpenOption;ILjava/lang/Object;)V
|
||||||
|
|||||||
Reference in New Issue
Block a user