diff --git a/libraries/stdlib/jdk7/src/kotlin/io/path/PathReadWrite.kt b/libraries/stdlib/jdk7/src/kotlin/io/path/PathReadWrite.kt index 352b3b311f6..6022e323ef8 100644 --- a/libraries/stdlib/jdk7/src/kotlin/io/path/PathReadWrite.kt +++ b/libraries/stdlib/jdk7/src/kotlin/io/path/PathReadWrite.kt @@ -18,6 +18,9 @@ import java.nio.file.StandardOpenOption /** * Returns a new [InputStreamReader] for reading the content of this file. + * + * @param charset character set to use for reading text, UTF-8 by default. + * @param options options to determine how the file is opened. */ @SinceKotlin("1.4") @ExperimentalPathApi @@ -29,9 +32,9 @@ public inline fun Path.reader(charset: Charset = Charsets.UTF_8, vararg options: /** * Returns a new [BufferedReader] for reading the content of this file. * - * @param charset character set to use. + * @param charset character set to use for reading text, UTF-8 by default. * @param bufferSize necessary size of the buffer. - * @param options options to determine how the file is opened + * @param options options to determine how the file is opened. */ @SinceKotlin("1.4") @ExperimentalPathApi @@ -46,6 +49,9 @@ public inline fun Path.bufferedReader( /** * Returns a new [OutputStreamWriter] for writing the content of this file. + * + * @param charset character set to use for writing text, UTF-8 by default. + * @param options options to determine how the file is opened. */ @SinceKotlin("1.4") @ExperimentalPathApi @@ -57,7 +63,7 @@ public inline fun Path.writer(charset: Charset = Charsets.UTF_8, vararg options: /** * Returns a new [BufferedWriter] for writing the content of this file. * - * @param charset character set to use. + * @param charset character set to use for writing text, UTF-8 by default. * @param bufferSize necessary size of the buffer. * @param options options to determine how the file is opened. */ @@ -87,7 +93,7 @@ public inline fun Path.readBytes(): ByteArray { } /** - * Write an [array] of bytes to this file. + * Writes an [array] of bytes to this file. * * By default, the file will be overwritten if it already exists, but you can control this behavior * with [options]. @@ -110,30 +116,33 @@ public inline fun Path.writeBytes(array: ByteArray, vararg options: OpenOption): @SinceKotlin("1.4") @ExperimentalPathApi @kotlin.internal.InlineOnly -public inline fun Path.appendBytes(array: ByteArray): Unit { - writeBytes(array, StandardOpenOption.APPEND) +public inline fun Path.appendBytes(array: ByteArray) { + Files.write(this, array, StandardOpenOption.APPEND) } /** - * Gets the entire content of this file as a String using UTF-8 or specified [charset]. + * Gets the entire content of this file as a String using UTF-8 or the specified [charset]. * * This method is not recommended on huge files. It has an internal limitation of 2 GB file size. * - * @param charset character set to use. + * @param charset character set to use for reading text, UTF-8 by default. * @return the entire content of this file as a String. */ @SinceKotlin("1.4") @ExperimentalPathApi -public fun Path.readText(charset: Charset = Charsets.UTF_8): String = readBytes().toString(charset) +@kotlin.internal.InlineOnly +public inline fun Path.readText(charset: Charset = Charsets.UTF_8): String = + readBytes().toString(charset) /** - * Sets the content of this file as [text] encoded using UTF-8 or specified [charset]. + * Sets the content of this file as [text] encoded using UTF-8 or the specified [charset]. * * By default, the file will be overwritten if it already exists, but you can control this behavior * with [options]. * * @param text text to write into file. - * @param charset character set to use. + * @param charset character set to use for writing text, UTF-8 by default. + * @param options options to determine how the file is opened. */ @SinceKotlin("1.4") @ExperimentalPathApi @@ -145,7 +154,7 @@ public fun Path.writeText(text: String, charset: Charset = Charsets.UTF_8, varar * Appends [text] to the content of this file using UTF-8 or the specified [charset]. * * @param text text to append to file. - * @param charset character set to use. + * @param charset character set to use for writing text, UTF-8 by default. */ @SinceKotlin("1.4") @ExperimentalPathApi @@ -160,7 +169,7 @@ public fun Path.appendText(text: String, charset: Charset = Charsets.UTF_8): Uni * You may use this function on huge files. * * @param options options to determine how the file is opened. - * @param charset character set to use. + * @param charset character set to use for reading text, UTF-8 by default. * @param action function to process file lines. */ @SinceKotlin("1.4") @@ -203,7 +212,7 @@ public inline fun Path.outputStream(vararg options: OpenOption): OutputStream { * * Do not use this function for huge files. * - * @param charset character set to use. By default uses UTF-8 charset. + * @param charset character set to use for reading text, UTF-8 by default. * @return list of file lines. */ @SinceKotlin("1.4") @@ -217,7 +226,7 @@ public inline fun Path.readLines(charset: Charset = Charsets.UTF_8): List Path.useLines(charset: Charset = Charsets.UTF_8, block: (Sequence) -> T): T { return bufferedReader(charset).use { block(it.lineSequence()) } } + +/** + * Write the specified collection of char sequences [lines] to a file terminating each one with the platform's line separator. + * + * By default, the file will be overwritten if it already exists, but you can control this behavior + * with [options]. + * + * @param charset character set to use for writing text, UTF-8 by default. + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.writeLines(lines: Iterable, charset: Charset = Charsets.UTF_8, vararg options: OpenOption): Path { + return Files.write(this, lines, charset, *options) +} + +/** + * Write the specified sequence of char sequences [lines] to a file terminating each one with the platform's line separator. + * + * By default, the file will be overwritten if it already exists, but you can control this behavior + * with [options]. + * + * @param charset character set to use for writing text, UTF-8 by default. + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.writeLines(lines: Sequence, charset: Charset = Charsets.UTF_8, vararg options: OpenOption): Path { + return Files.write(this, lines.asIterable(), charset, *options) +} + +/** + * Appends the specified collection of char sequences [lines] to a file terminating each one with the platform's line separator. + * + * @param charset character set to use for writing text, UTF-8 by default. + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.appendLines(lines: Iterable, charset: Charset = Charsets.UTF_8): Path { + return Files.write(this, lines, charset, StandardOpenOption.APPEND) +} + +/** + * Appends the specified sequence of char sequences [lines] to a file terminating each one with the platform's line separator. + * + * @param charset character set to use for writing text, UTF-8 by default. + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.appendLines(lines: Sequence, charset: Charset = Charsets.UTF_8): Path { + return Files.write(this, lines.asIterable(), charset, StandardOpenOption.APPEND) +} + + diff --git a/libraries/stdlib/jdk7/src/kotlin/io/path/PathUtils.kt b/libraries/stdlib/jdk7/src/kotlin/io/path/PathUtils.kt index c637f43bbab..4316fb8ee1b 100644 --- a/libraries/stdlib/jdk7/src/kotlin/io/path/PathUtils.kt +++ b/libraries/stdlib/jdk7/src/kotlin/io/path/PathUtils.kt @@ -10,12 +10,33 @@ package kotlin.io.path import java.io.IOException +import java.net.URI import java.nio.file.* import java.nio.file.FileAlreadyExistsException import java.nio.file.NoSuchFileException +import java.nio.file.attribute.* /** - * Returns the extension of this path (not including the dot), or an empty string if it doesn't have one. + * Returns the name of the file or directory denoted by this path as a string, + * or an empty string if this path has zero path elements. + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +public val Path.name: String + get() = fileName?.toString().orEmpty() + +/** + * Returns the [name][Path.name] of this file or directory without an extension, + * or an empty string if this path has zero path elements. + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +public val Path.nameWithoutExtension: String + get() = fileName?.toString()?.substringBeforeLast(".") ?: "" + +/** + * Returns the extension of this path (not including the dot), + * or an empty string if it doesn't have one. */ @SinceKotlin("1.4") @ExperimentalPathApi @@ -34,21 +55,14 @@ public val Path.invariantSeparatorsPath: String return if (separator != "/") toString().replace(separator, "/") else toString() } -/** - * Returns this path's [fileName][Path.getFileName] without an extension, or an empty string if - * this path has zero elements. - */ -@SinceKotlin("1.4") -@ExperimentalPathApi -public val Path.nameWithoutExtension: String - get() = fileName?.toString()?.substringBeforeLast(".") ?: "" /** * Calculates the relative path for this path from a [base] path. + * * Note that the [base] path is treated as a directory. * If this path matches the [base] path, then a [Path] with an empty path will be returned. * - * @return Path with relative path from [base] to this. + * @return the relative path from [base] to this. * * @throws IllegalArgumentException if this and base paths have different roots. */ @@ -57,15 +71,16 @@ public val Path.nameWithoutExtension: String public fun Path.relativeTo(base: Path): Path = try { PathRelativizer.tryRelativeTo(this, base) } catch (e: IllegalArgumentException) { - throw java.lang.IllegalArgumentException(e.message + "\nthis path: $this\nbase path: $base", e) + throw IllegalArgumentException(e.message + "\nthis path: $this\nbase path: $base", e) } /** * Calculates the relative path for this path from a [base] path. + * * Note that the [base] path is treated as a directory. * If this path matches the [base] path, then a [Path] with an empty path will be returned. * - * @return Path with relative path from [base] to this, or `this` if this and base paths have different roots. + * @return the relative path from [base] to this, or `this` if this and base paths have different roots. */ @SinceKotlin("1.4") @ExperimentalPathApi @@ -74,10 +89,11 @@ public fun Path.relativeToOrSelf(base: Path): Path = /** * Calculates the relative path for this path from a [base] path. + * * Note that the [base] path is treated as a directory. * If this path matches the [base] path, then a [Path] with an empty path will be returned. * - * @return Path with relative path from [base] to this, or `null` if this and base paths have different roots. + * @return the relative path from [base] to this, or `null` if this and base paths have different roots. */ @SinceKotlin("1.4") @ExperimentalPathApi @@ -87,7 +103,7 @@ public fun Path.relativeToOrNull(base: Path): Path? = try { null } -internal object PathRelativizer { +private object PathRelativizer { private val emptyPath = Paths.get("") private val parentPath = Paths.get("..") @@ -117,9 +133,9 @@ internal object PathRelativizer { } /** - * Copies this path to the given [target] path. + * Copies a file or directory located by this path to the given [target] path. * - * Unlike `File.copyTo`, if some directories on a way to the [target] are missing, then they won't be created automatically. + * Unlike `File.copyTo`, if some directories on the way to the [target] are missing, then they won't be created automatically. * You can use the following approach to ensure that required intermediate directories are created: * ``` * sourcePath.copyTo(destinationPath.apply { parent?.createDirectories() }) @@ -141,18 +157,21 @@ internal object PathRelativizer { * @throws DirectoryNotEmptyException if the destination path point to an existing directory and [overwrite] argument is `true`, * when the directory being replaced is not empty. * @throws IOException if any errors occur while copying. + * + * @see Files.copy */ @SinceKotlin("1.4") @ExperimentalPathApi -public fun Path.copyTo(target: Path, overwrite: Boolean = false): Path { - val options = if (overwrite) arrayOf(StandardCopyOption.REPLACE_EXISTING) else emptyArray() - return copyTo(target, *options) +@kotlin.internal.InlineOnly +public inline fun Path.copyTo(target: Path, overwrite: Boolean = false): Path { + val options = if (overwrite) arrayOf(StandardCopyOption.REPLACE_EXISTING) else emptyArray() + return Files.copy(this, target, *options) } /** - * Copies this path to the given [target] path. + * Copies a file or directory located by this path to the given [target] path. * - * Unlike `File.copyTo`, if some directories on a way to the [target] are missing, then they won't be created automatically. + * Unlike `File.copyTo`, if some directories on the way to the [target] are missing, then they won't be created automatically. * You can use the following approach to ensure that required intermediate directories are created: * ``` * sourcePath.copyTo(destinationPath.apply { parent?.createDirectories() }) @@ -177,17 +196,25 @@ public fun Path.copyTo(target: Path, overwrite: Boolean = false): Path { * @throws DirectoryNotEmptyException if the destination path point to an existing directory and [REPLACE_EXISTING][StandardCopyOption.REPLACE_EXISTING] is used, * when the directory being replaced is not empty. * @throws IOException if any errors occur while copying. + * + * @see Files.copy */ @SinceKotlin("1.4") @ExperimentalPathApi -public fun Path.copyTo(target: Path, vararg options: CopyOption): Path { +@kotlin.internal.InlineOnly +public inline fun Path.copyTo(target: Path, vararg options: CopyOption): Path { return Files.copy(this, target, *options) } /** - * Check if this path exists. + * Checks if the file located by this path exists. * - * @param options Options to control how symbolic links are handled. + * @return `true`, if the file definitely exists, `false` otherwise, + * including situations when the existence cannot be determined. + * + * @param options options to control how symbolic links are handled. + * + * @see Files.exists */ @SinceKotlin("1.4") @ExperimentalPathApi @@ -195,9 +222,14 @@ public fun Path.copyTo(target: Path, vararg options: CopyOption): Path { public inline fun Path.exists(vararg options: LinkOption): Boolean = Files.exists(this, *options) /** - * Check if this path does not exist. + * Checks if the file located by this path does not exist. * - * @param options Options to control how symbolic links are handled. + * @return `true`, if the file definitely does not exist, `false` otherwise, + * including situations when the existence cannot be determined. + * + * @param options options to control how symbolic links are handled. + * + * @see Files.notExists */ @SinceKotlin("1.4") @ExperimentalPathApi @@ -205,9 +237,11 @@ public inline fun Path.exists(vararg options: LinkOption): Boolean = Files.exist public inline fun Path.notExists(vararg options: LinkOption): Boolean = Files.notExists(this, *options) /** - * Check if this path is a file. + * Checks if the file located by this path is a regular file. * - * @param options Options to control how symbolic links are handled. + * @param options options to control how symbolic links are handled. + * + * @see Files.isRegularFile */ @SinceKotlin("1.4") @ExperimentalPathApi @@ -215,11 +249,13 @@ public inline fun Path.notExists(vararg options: LinkOption): Boolean = Files.no public inline fun Path.isRegularFile(vararg options: LinkOption): Boolean = Files.isRegularFile(this, *options) /** - * Check if this path is a directory. + * Checks if the file located by this path is a directory. * * By default, symbolic links in the path are followed. * - * @param options Options to control how symbolic links are handled. + * @param options options to control how symbolic links are handled. + * + * @see Files.isDirectory */ @SinceKotlin("1.4") @ExperimentalPathApi @@ -227,7 +263,9 @@ public inline fun Path.isRegularFile(vararg options: LinkOption): Boolean = File public inline fun Path.isDirectory(vararg options: LinkOption): Boolean = Files.isDirectory(this, *options) /** - * Check if this path exists and is a symbolic link. + * Checks if the file located by this path exists and is a symbolic link. + * + * @see Files.isSymbolicLink */ @SinceKotlin("1.4") @ExperimentalPathApi @@ -235,7 +273,9 @@ public inline fun Path.isDirectory(vararg options: LinkOption): Boolean = Files. public inline fun Path.isSymbolicLink(): Boolean = Files.isSymbolicLink(this) /** - * Check if this path exists and is executable. + * Checks if the file located by this path exists and is executable. + * + * @see Files.isExecutable */ @SinceKotlin("1.4") @ExperimentalPathApi @@ -243,10 +283,12 @@ public inline fun Path.isSymbolicLink(): Boolean = Files.isSymbolicLink(this) public inline fun Path.isExecutable(): Boolean = Files.isExecutable(this) /** - * Check if this path is considered hidden. + * Checks if the file located by this path is considered hidden. * * This check is dependant on the current filesystem. For example, on UNIX-like operating systems, a * path is considered hidden if its name begins with a dot. On Windows, file attributes are checked. + * + * @see Files.isHidden */ @SinceKotlin("1.4") @ExperimentalPathApi @@ -254,7 +296,9 @@ public inline fun Path.isExecutable(): Boolean = Files.isExecutable(this) public inline fun Path.isHidden(): Boolean = Files.isHidden(this) /** - * Check if this path exists and is readable. + * Checks if the file located by this path exists and is readable. + * + * @see Files.isReadable */ @SinceKotlin("1.4") @ExperimentalPathApi @@ -262,7 +306,9 @@ public inline fun Path.isHidden(): Boolean = Files.isHidden(this) public inline fun Path.isReadable(): Boolean = Files.isReadable(this) /** - * Check that this path exists and is writable. + * Checks if the file located by this path exists and is writable. + * + * @see Files.isWritable */ @SinceKotlin("1.4") @ExperimentalPathApi @@ -270,21 +316,23 @@ public inline fun Path.isReadable(): Boolean = Files.isReadable(this) public inline fun Path.isWritable(): Boolean = Files.isWritable(this) /** - * Check if this path points to the same file or directory as [other]. + * Checks if the file located by this path points to the same file or directory as [other]. + * + * @see Files.isSameFile */ @SinceKotlin("1.4") @ExperimentalPathApi @kotlin.internal.InlineOnly -public inline fun Path.isSameFile(other: Path): Boolean = Files.isSameFile(this, other) +public inline fun Path.isSameFileAs(other: Path): Boolean = Files.isSameFile(this, other) /** - * Return a list of the entries in this directory optionally filtered by matching against the specified [glob] pattern. + * Returns a list of the entries in this directory optionally filtered by matching against the specified [glob] pattern. * * @param glob the globbing pattern. The syntax is specified by the [FileSystem.getPathMatcher] method. * * @throws java.util.regex.PatternSyntaxException if the glob pattern is invalid. - * @throws NotDirectoryException If this path does not refer to a directory - * @throws IOException If an I/O error occurs + * @throws NotDirectoryException If this path does not refer to a directory. + * @throws IOException If an I/O error occurs. * * @see Files.newDirectoryStream */ @@ -295,37 +343,564 @@ public fun Path.listDirectoryEntries(glob: String = "*"): List { } /** - * Call the [block] callback with a sequence of all entries in this directory + * Calls the [block] callback with a sequence of all entries in this directory * optionally filtered by matching against the specified [glob] pattern. * * @param glob the globbing pattern. The syntax is specified by the [FileSystem.getPathMatcher] method. * * @throws java.util.regex.PatternSyntaxException if the glob pattern is invalid. - * @throws NotDirectoryException If this path does not refer to a directory - * @throws IOException If an I/O error occurs - * @return the value returned by [block] + * @throws NotDirectoryException If this path does not refer to a directory. + * @throws IOException If an I/O error occurs. + * @return the value returned by [block]. * * @see Files.newDirectoryStream */ @SinceKotlin("1.4") @ExperimentalPathApi -public fun Path.useDirectoryEntries(glob: String = "*", block: (Sequence) -> T): T { +@kotlin.internal.InlineOnly +public inline fun Path.useDirectoryEntries(glob: String = "*", block: (Sequence) -> T): T { return Files.newDirectoryStream(this, glob).use { block(it.asSequence()) } } /** - * Perform the given [action] on each entry in this directory optionally filtered by matching against the specified [glob] pattern. + * Performs the given [action] on each entry in this directory optionally filtered by matching against the specified [glob] pattern. * * @param glob the globbing pattern. The syntax is specified by the [FileSystem.getPathMatcher] method. * * @throws java.util.regex.PatternSyntaxException if the glob pattern is invalid. - * @throws NotDirectoryException If this path does not refer to a directory - * @throws IOException If an I/O error occurs + * @throws NotDirectoryException If this path does not refer to a directory. + * @throws IOException If an I/O error occurs. * * @see Files.newDirectoryStream */ @SinceKotlin("1.4") @ExperimentalPathApi -public fun Path.forEachDirectoryEntry(glob: String = "*", action: (Path) -> Unit) { +@kotlin.internal.InlineOnly +public inline fun Path.forEachDirectoryEntry(glob: String = "*", action: (Path) -> Unit) { return Files.newDirectoryStream(this, glob).use { it.forEach(action) } } + +/** + * Returns the size of a regular file as a [Long] value of bytes or throws an exception if the file doesn't exist. + * + * @throws IOException if an I/O error occurred. + * @see Files.size + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.fileSize(): Long = + Files.size(this) + +/** + * Deletes the existing file or empty directory specified by this path. + * + * @throws NoSuchFileException if the file or directory does not exist. + * @throws DirectoryNotEmptyException if the directory exists but is not empty. + * + * @see Files.delete + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.deleteExisting() { + Files.delete(this) +} + +/** + * Deletes the file or empty directory specified by this path if it exists. + * + * @return `true` if the existing file was successfully deleted, `false` if the file does not exist. + * + * @throws DirectoryNotEmptyException if the directory exists but is not empty + * + * @see Files.deleteIfExists + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.deleteIfExists() = + Files.deleteIfExists(this) + +/** + * Creates a new directory or throws an exception if there is already a file or directory located by this path. + * + * Note that the parent directory where this directory is going to be created must already exist. + * If you need to create all non-existent parent directories, use [Path.createDirectories]. + * + * @param attributes an optional list of file attributes to set atomically when creating the directory. + * + * @throws FileAlreadyExistsException if there is already a file or directory located by this path + * (optional specific exception, some implementations may throw more general [IOException]). + * @throws IOException if an I/O error occurs or the parent directory does not exist. + * @throws UnsupportedOperationException if the [attributes ]array contains an attribute that cannot be set atomically + * when creating the directory. + * + * @see Files.createDirectory + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.createDirectory(vararg attributes: FileAttribute<*>): Path = + Files.createDirectory(this, *attributes) + +/** + * Creates a directory ensuring that all nonexistent parent directories exist by creating them first. + * + * If the directory already exists, this function does not throw an exception, unlike [Path.createDirectory]. + * + * @param attributes an optional list of file attributes to set atomically when creating the directory. + * + * @throws FileAlreadyExistsException if there is already a file located by this path + * (optional specific exception, some implementations may throw more general [IOException]). + * @throws IOException if an I/O error occurs. + * @throws UnsupportedOperationException if the [attributes ]array contains an attribute that cannot be set atomically + * when creating the directory. + * + * @see Files.createDirectories + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.createDirectories(vararg attributes: FileAttribute<*>): Path = + Files.createDirectories(this, *attributes) + + +/** + * Moves or renames the file located by this path to the [target] path. + * + * @param options options specifying how the move should be done, see [StandardCopyOption], [LinkOption]. + * + * @throws FileAlreadyExistsException if the target file exists but cannot be replaced because the + * [StandardCopyOption.REPLACE_EXISTING] option is not specified (optional specific exception). + * @throws DirectoryNotEmptyException the [StandardCopyOption.REPLACE_EXISTING] option is specified but the file + * cannot be replaced because it is a non-empty directory, or the + * source is a non-empty directory containing entries that would + * be required to be moved (optional specific exception). + * + * @see Files.move + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.moveTo(target: Path, vararg options: CopyOption): Path = + Files.move(this, target, *options) + +/** + * Moves or renames the file located by this path to the [target] path. + * + * @param overwrite allows to overwrite the target if it already exists. + * + * @throws FileAlreadyExistsException if the target file exists but cannot be replaced because the + * `overwrite = true` option is not specified (optional specific exception). + * @throws DirectoryNotEmptyException the `overwrite = true` option is specified but the file + * cannot be replaced because it is a non-empty directory, or the + * source is a non-empty directory containing entries that would + * be required to be moved (optional specific exception). + * + * @see Files.move + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.moveTo(target: Path, overwrite: Boolean = false): Path { + val options = if (overwrite) arrayOf(StandardCopyOption.REPLACE_EXISTING) else emptyArray() + return Files.move(this, target, *options) +} + +/** + * Returns the [FileStore] representing the file store where a file is located. + * + * @see Files.getFileStore + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.fileStore(): FileStore = + Files.getFileStore(this) + +/** + * Reads the value of a file attribute. + * + * The attribute name is specified with the [attribute] parameter optionally prefixed with the attribute view name: + * ``` + * [view_name:]attribute_name + * ``` + * When the view name is not specified, it defaults to `basic`. + * + * @throws UnsupportedOperationException if the attribute view is not supported. + * @throws IllegalArgumentException if the attribute name is not specified or is not recognized. + * @see Files.getAttribute + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.getAttribute(attribute: String, vararg options: LinkOption): Any? = + Files.getAttribute(this, attribute, *options) + +/** + * Sets the value of a file attribute. + * + * The attribute name is specified with the [attribute] parameter optionally prefixed with the attribute view name: + * ``` + * [view_name:]attribute_name + * ``` + * When the view name is not specified, it defaults to `basic`. + * + * @throws UnsupportedOperationException if the attribute view is not supported. + * @throws IllegalArgumentException if the attribute name is not specified or is not recognized, or + * the attribute value is of the correct type but has an inappropriate value. + * @throws ClassCastException if the attribute value is not of the expected type + * @see Files.setAttribute + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.setAttribute(attribute: String, value: Any?, vararg options: LinkOption): Path = + Files.setAttribute(this, attribute, value, *options) + +/** + * Returns a file attributes view of a given type [V] + * or `null` if the requested attribute view type is not available. + * + * The returned view allows to read and optionally to modify attributes of a file. + * + * @param V the reified type of the desired attribute view. + * + * @see Files.getFileAttributeView + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.fileAttributesViewOrNull(vararg options: LinkOption): V? = + Files.getFileAttributeView(this, V::class.java, *options) + +/** + * Returns a file attributes view of a given type [V] + * or throws an [UnsupportedOperationException] if the requested attribute view type is not available.. + * + * The returned view allows to read and optionally to modify attributes of a file. + * + * @param V the reified type of the desired attribute view, a subtype of [FileAttributeView]. + * + * @see Files.getFileAttributeView + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.fileAttributesView(vararg options: LinkOption): V = + Files.getFileAttributeView(this, V::class.java, *options) ?: fileAttributeViewNotAvailable(this, V::class.java) + +@PublishedApi +internal fun fileAttributeViewNotAvailable(path: Path, attributeViewClass: Class<*>): Nothing = + throw UnsupportedOperationException("The desired attribute view type $attributeViewClass is not available for the file $path.") + +/** + * Reads a file's attributes of the specified type [A] in bulk. + * + * @param A the reified type of the desired attributes, a subtype of [BasicFileAttributes]. + * + * @throws UnsupportedOperationException if the given attributes type [A] is not supported. + * @see Files.readAttributes + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.readAttributes(vararg options: LinkOption): A = + Files.readAttributes(this, A::class.java, *options) + +/** + * Reads the specified list of attributes of a file in bulk. + * + * The list of [attributes] to read is specified in the following string form: + * ``` + * [view:]attribute_name1[,attribute_name2...] + * ``` + * So the names are comma-separated and optionally prefixed by the attribute view type name, `basic` by default. + * The special `*` attribute name can be used to read all attributes of the specified view. + * + * @return a [Map][Map] having an entry for an each attribute read, where the key is the attribute name and the value is the attribute value. + * @throws UnsupportedOperationException if the attribute view is not supported. + * @throws IllegalArgumentException if no attributes are specified or an unrecognized attribute is specified. + * @see Files.readAttributes + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.readAttributes(attributes: String, vararg options: LinkOption): Map = + Files.readAttributes(this, attributes, *options) + +/** + * Returns the last modified time of the file located by this path. + * + * If the file system does not support modification timestamps, some implementation-specific default is returned. + * + * @see Files.getLastModifiedTime + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.getLastModifiedTime(vararg options: LinkOption): FileTime = + Files.getLastModifiedTime(this, *options) + +/** + * Sets the last modified time attribute for the file located by this path. + * + * If the file system does not support modification timestamps, the behavior of this method is not defined. + * + * @see Files.setLastModifiedTime + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.setLastModifiedTime(value: FileTime): Path = + Files.setLastModifiedTime(this, value) + +/** + * Returns the owner of a file. + * + * @throws UnsupportedOperationException if the associated file system does not support the [FileOwnerAttributeView]. + * + * @see Files.getOwner + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.getOwner(vararg options: LinkOption): UserPrincipal? = + Files.getOwner(this, *options) + +/** + * Sets the file owner to the specified [value]. + * + * @throws UnsupportedOperationException if the associated file system does not support the [FileOwnerAttributeView]. + * + * @see Files.setOwner + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.setOwner(value: UserPrincipal): Path = + Files.setOwner(this, value) + +/** + * Returns the POSIX file permissions of the file located by this path. + * + * @throws UnsupportedOperationException if the associated file system does not support the [PosixFileAttributeView]. + * + * @see Files.getPosixFilePermissions + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.getPosixFilePermissions(vararg options: LinkOption): Set = + Files.getPosixFilePermissions(this, *options) + +/** + * Sets the POSIX file permissions for the file located by this path. + * + * @throws UnsupportedOperationException if the associated file system does not support the [PosixFileAttributeView]. + * + * @see Files.setPosixFilePermissions + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.setPosixFilePermissions(value: Set): Path = + Files.setPosixFilePermissions(this, value) + +/** + * Creates a new link (directory entry) located by this path for the existing file [target]. + * + * Calling this function may require the process to be started with implementation specific privileges to create hard links + * or to create links to directories. + * + * @throws FileAlreadyExistsException if a file with this name already exists + * (optional specific exception, some implementations may throw a more general one). + * @throws UnsupportedOperationException if the implementation does not support creating a hard link. + * + * @see Files.createLink + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.createLinkPointingTo(target: Path): Path = + Files.createLink(this, target) + +/** + * Creates a new symbolic link located by this path to the given [target]. + * + * Calling this function may require the process to be started with implementation specific privileges to + * create symbolic links. + * + * @throws FileAlreadyExistsException if a file with this name already exists + * (optional specific exception, some implementations may throw a more general one). + * @throws UnsupportedOperationException if the implementation does not support symbolic links or the + * [attributes] array contains an attribute that cannot be set atomically when creating the symbolic link. + * + * @see Files.createSymbolicLink + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.createSymbolicLinkPointingTo(target: Path, vararg attributes: FileAttribute<*>): Path = + Files.createSymbolicLink(this, target, *attributes) + +/** + * Reads the target of a symbolic link located by this path. + * + * @throws UnsupportedOperationException if symbolic links are not supported by this implementation. + * @throws NotLinkException if the target is not a symbolic link + * (optional specific exception, some implementations may throw a more general one). + * + * @see Files.readSymbolicLink + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.readSymbolicLink(): Path = + Files.readSymbolicLink(this) + +/** + * Creates a new and empty file specified by this path, failing if the file already exists. + * + * @param attributes an optional list of file attributes to set atomically when creating the file. + * + * @throws FileAlreadyExistsException if a file specified by this path already exists + * (optional specific exception, some implementations may throw more general [IOException]). + * @throws UnsupportedOperationException if the [attributes] array contains an attribute that cannot be set atomically + * when creating the file. + * + * @see Files.createFile + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path.createFile(vararg attributes: FileAttribute<*>): Path = + Files.createFile(this, *attributes) + +/** + * Creates an empty file in the default temp directory, using + * the given [prefix] and [suffix] to generate its name. + * + * @param attributes an optional list of file attributes to set atomically when creating the file. + * @return the path to the newly created file that did not exist before. + * + * @throws UnsupportedOperationException if the array contains an attribute that cannot be set atomically + * when creating the file. + * + * @see Files.createTempFile + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun createTempFile(prefix: String? = null, suffix: String? = null, vararg attributes: FileAttribute<*>): Path = + Files.createTempFile(prefix, suffix, *attributes) + +/** + * Creates an empty file in the specified [directory], using + * the given [prefix] and [suffix] to generate its name. + * + * @param attributes an optional list of file attributes to set atomically when creating the file. + * @return the path to the newly created file that did not exist before. + * + * @throws UnsupportedOperationException if the array contains an attribute that cannot be set atomically + * when creating the file. + * + * @see Files.createTempFile + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun createTempFile(directory: Path, prefix: String? = null, suffix: String? = null, vararg attributes: FileAttribute<*>): Path = + Files.createTempFile(directory, prefix, suffix, *attributes) + +/** + * Creates a new directory in the default temp directory, using the given [prefix] to generate its name. + * + * @param attributes an optional list of file attributes to set atomically when creating the directory. + * @return the path to the newly created directory that did not exist before. + * + * @throws UnsupportedOperationException if the array contains an attribute that cannot be set atomically + * when creating the directory. + * + * @see Files.createTempDirectory + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun createTempDirectory(prefix: String? = null, vararg attributes: FileAttribute<*>): Path = + Files.createTempDirectory(prefix, *attributes) + +/** + * Creates a new directory in the specified [directory], using the given [prefix] to generate its name. + * + * @param attributes an optional list of file attributes to set atomically when creating the directory. + * @return the path to the newly created directory that did not exist before. + * + * @throws UnsupportedOperationException if the array contains an attribute that cannot be set atomically + * when creating the directory. + * + * @see Files.createTempDirectory + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun createTempDirectory(directory: Path, prefix: String? = null, vararg attributes: FileAttribute<*>): Path = + Files.createTempDirectory(directory, prefix, *attributes) + +/** + * Resolves the given [other] path against this path. + * + * This operator is a shortcut for the [Path.resolve] function. + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline operator fun Path.div(other: Path): Path = + this.resolve(other) + +/** + * Resolves the given [other] path string against this path. + * + * This operator is a shortcut for the [Path.resolve] function. + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline operator fun Path.div(other: String): Path = + this.resolve(other) + + +/** + * Converts the provided [path] string to a [Path] object of the [default][FileSystems.getDefault] filesystem. + * + * @see Paths.get + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path(path: String): Path = + Paths.get(path) + +/** + * Converts the name sequence specified with the [base] path string and a number of [subpaths] additional names + * to a [Path] object of the [default][FileSystems.getDefault] filesystem. + * + * @see Paths.get + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun Path(base: String, vararg subpaths: String): Path = + Paths.get(base, *subpaths) + +/** + * Converts this URI to a [Path] object. + * + * @see Paths.get + */ +@SinceKotlin("1.4") +@ExperimentalPathApi +@kotlin.internal.InlineOnly +public inline fun URI.toPath(): Path = + Paths.get(this) diff --git a/libraries/stdlib/jdk7/test/PathExtensionsTest.kt b/libraries/stdlib/jdk7/test/PathExtensionsTest.kt index 582a928079c..84c542acc63 100644 --- a/libraries/stdlib/jdk7/test/PathExtensionsTest.kt +++ b/libraries/stdlib/jdk7/test/PathExtensionsTest.kt @@ -7,38 +7,62 @@ package kotlin.jdk7.test import java.io.IOException import java.nio.file.* +import java.nio.file.attribute.* import kotlin.io.path.* +import kotlin.random.Random import kotlin.test.* class PathExtensionsTest { - private val isCaseInsensitiveFileSystem = Paths.get("C:/") == Paths.get("c:/") + private val isCaseInsensitiveFileSystem = Path("C:/") == Path("c:/") private val isBackslashSeparator = FileSystems.getDefault().separator == "\\" @Test - fun extension() { - assertEquals("bbb", Paths.get("aaa.bbb").extension) - assertEquals("", Paths.get("aaa").extension) - assertEquals("", Paths.get("aaa.").extension) - assertEquals("bbb", Paths.get(".bbb").extension) - assertEquals("", Paths.get("/my.dir/log").extension) - assertEquals("", Paths.get("/").extension) + fun filenameComponents() { + fun check(path: String, name: String, nameNoExt: String, extension: String) { + val p = Path(path) + assertEquals(name, p.name, "name") + assertEquals(nameNoExt, p.nameWithoutExtension, "nameWithoutExtension") + assertEquals(extension, p.extension, "extension") + } + + check(path = "aaa.bbb", name = "aaa.bbb", nameNoExt = "aaa", extension = "bbb") + check(path = "aaa", name = "aaa", nameNoExt = "aaa", extension = "") + check(path = "aaa.", name = "aaa.", nameNoExt = "aaa", extension = "") + check(path = ".aaa", name = ".aaa", nameNoExt = "", extension = "aaa") + check(path = "/dir.ext/aaa.bbb", name = "aaa.bbb", nameNoExt = "aaa", extension = "bbb") + check(path = "/dir.ext/aaa", name = "aaa", nameNoExt = "aaa", extension = "") + check(path = "/", name = "", nameNoExt = "", extension = "") + check(path = "", name = "", nameNoExt = "", extension = "") } @Test - fun nameWithoutExtension() { - assertEquals("aaa", Paths.get("aaa.bbb").nameWithoutExtension) - assertEquals("aaa", Paths.get("aaa").nameWithoutExtension) - assertEquals("aaa", Paths.get("aaa.").nameWithoutExtension) - assertEquals("", Paths.get(".bbb").nameWithoutExtension) - assertEquals("log", Paths.get("/my.dir/log").nameWithoutExtension) - assertEquals("", Paths.get("").nameWithoutExtension) - assertEquals("", Paths.get("/").nameWithoutExtension) + fun invariantSeparators() { + val path = Path("base") / "nested" / "leaf" + assertEquals("base/nested/leaf", path.invariantSeparatorsPath) + + val path2 = Path("base", "nested", "leaf") + assertEquals("base/nested/leaf", path2.invariantSeparatorsPath) } @Test - fun testCopyTo() { - val srcFile = Files.createTempFile(null, null) - val dstFile = Files.createTempFile(null, null) + fun createNewFile() { + val dir = createTempDirectory() + + val file = dir / "new-file" + + assertTrue(file.notExists()) + + file.createFile() + assertTrue(file.exists()) + assertTrue(file.isRegularFile()) + + assertFailsWith { file.createFile() } + } + + @Test + fun copyTo() { + val srcFile = createTempFile() + val dstFile = createTempFile() try { srcFile.writeText("Hello, World!") assertFailsWith("copy do not overwrite existing file") { @@ -53,35 +77,34 @@ class PathExtensionsTest { srcFile.copyTo(srcFile, overwrite = true) compareFiles(dst, srcFile, "copying file to itself leaves it intact") - assertTrue(Files.deleteIfExists(dstFile)) + assertTrue(dstFile.deleteIfExists()) dst = srcFile.copyTo(dstFile) compareFiles(srcFile, dst, "copy to new file") val subDst = dstFile.resolve("foo/bar") - assertFailsWith { srcFile.copyTo(subDst) } - assertFailsWith { srcFile.copyTo(subDst, overwrite = true) } - assertTrue(Files.deleteIfExists(dstFile)) - assertFailsWith { srcFile.copyTo(subDst) } + assertFailsWith { srcFile.copyTo(subDst) } + assertFailsWith { srcFile.copyTo(subDst, overwrite = true) } + assertTrue(dstFile.deleteIfExists()) + assertFailsWith { srcFile.copyTo(subDst) } - Files.createDirectory(dstFile) - val child = dstFile.resolve("child") - Files.createFile(child) - assertFailsWith( "copy with overwrite do not overwrite non-empty dir") { + dstFile.createDirectory() + val child = dstFile.resolve("child").createFile() + assertFailsWith("copy with overwrite do not overwrite non-empty dir") { srcFile.copyTo(dstFile, overwrite = true) } - Files.delete(child) + child.deleteExisting() srcFile.copyTo(dstFile, overwrite = true) assertEquals(srcFile.readText(), dstFile.readText(), "copy with overwrite over empty dir") - assertTrue(Files.deleteIfExists(srcFile)) - assertTrue(Files.deleteIfExists(dstFile)) + assertTrue(srcFile.deleteIfExists()) + assertTrue(dstFile.deleteIfExists()) assertFailsWith { srcFile.copyTo(dstFile) } - Files.createDirectory(srcFile) + srcFile.createDirectory() srcFile.resolve("somefile").writeText("some content") dstFile.writeText("") assertFailsWith("copy dir do not overwrite file") { @@ -111,35 +134,141 @@ class PathExtensionsTest { @Test fun copyToNameWithoutParent() { - val currentDir = Paths.get("").toAbsolutePath() - val srcFile = Files.createTempFile(null, null) - val dstFile = Files.createTempFile(currentDir, null, null) + val currentDir = Path("").toAbsolutePath() + val srcFile = createTempFile() + val dstFile = createTempFile(directory = currentDir) try { srcFile.writeText("Hello, World!", Charsets.UTF_8) - Files.delete(dstFile) + dstFile.deleteExisting() - val dstRelative = Paths.get(dstFile.fileName.toString()) + val dstRelative = Path(dstFile.name) srcFile.copyTo(dstRelative) assertEquals(srcFile.readText(), dstFile.readText()) } finally { - Files.delete(dstFile) - Files.delete(srcFile) + dstFile.deleteExisting() + srcFile.deleteExisting() + } + } + + @Test + fun moveTo() { + val original = createTempFile() + val srcFile = createTempFile() + val dstFile = createTempFile() + fun restoreSrcFile() { original.copyTo(srcFile, overwrite = true) } + try { + original.writeText("Hello, World!") + restoreSrcFile() + + assertFailsWith("do not overwrite existing file") { + srcFile.moveTo(dstFile) + } + + var dst = srcFile.moveTo(dstFile, overwrite = true) + assertSame(dst, dstFile) + compareFiles(original, dst, "move with overwrite over existing file") + assertTrue(srcFile.notExists()) + + restoreSrcFile() + srcFile.moveTo(srcFile) + srcFile.moveTo(srcFile, overwrite = true) + + compareFiles(original, srcFile, "move file to itself leaves it intact") + + assertTrue(dstFile.deleteIfExists()) + dst = srcFile.moveTo(dstFile) + compareFiles(original, dst, "move to new file") + + restoreSrcFile() + val subDst = dstFile.resolve("foo/bar") + assertFailsWith { srcFile.moveTo(subDst) } + assertFailsWith { srcFile.moveTo(subDst, overwrite = true) } + assertTrue(dstFile.deleteIfExists()) + assertFailsWith { srcFile.moveTo(subDst) } + + dstFile.createDirectory() + val child = dstFile.resolve("child").createFile() + assertFailsWith("move with overwrite do not overwrite non-empty dir") { + srcFile.moveTo(dstFile, overwrite = true) + } + child.deleteExisting() + + srcFile.moveTo(dstFile, overwrite = true) + compareFiles(original, dstFile, "move with overwrite over empty dir") + + assertTrue(srcFile.notExists()) + assertTrue(dstFile.deleteIfExists()) + + assertFailsWith { + srcFile.moveTo(dstFile) + } + + srcFile.createDirectory() + srcFile.resolve("somefile").writeText("some content") + dstFile.writeText("") + assertFailsWith("move dir do not overwrite file") { + srcFile.moveTo(dstFile) + } + srcFile.moveTo(dstFile, overwrite = true) + assertTrue(dstFile.isDirectory()) + assertEquals(listOf(dstFile / "somefile"), dstFile.listDirectoryEntries(), "directory is moved with its content") + } finally { + srcFile.toFile().deleteRecursively() + dstFile.toFile().deleteRecursively() } } private fun compareFiles(src: Path, dst: Path, message: String? = null) { assertTrue(dst.exists()) assertEquals(src.isRegularFile(), dst.isRegularFile(), message) + assertEquals(src.isDirectory(), dst.isDirectory(), message) if (dst.isRegularFile()) { assertTrue(src.readBytes().contentEquals(dst.readBytes()), message) } } @Test - fun testAttributeGettersOnFile() { - val file = Files.createTempFile(null, null) + fun fileSize() { + val file = createTempFile() + assertEquals(0, file.fileSize()) + + file.writeBytes(ByteArray(100)) + assertEquals(100, file.fileSize()) + + file.appendText("Hello", Charsets.US_ASCII) + assertEquals(105, file.fileSize()) + + file.deleteExisting() + assertFailsWith { file.fileSize() } + } + + @Test + fun deleteExisting() { + val file = createTempFile() + file.deleteExisting() + assertFailsWith { file.deleteExisting() } + + val dir = createTempDirectory() + dir.deleteExisting() + assertFailsWith { dir.deleteExisting() } + } + + @Test + fun deleteIfExists() { + val file = createTempFile() + assertTrue(file.deleteIfExists()) + assertFalse(file.deleteIfExists()) + + val dir = createTempDirectory() + assertTrue(dir.deleteIfExists()) + assertFalse(dir.deleteIfExists()) + } + + @Test + fun attributeGettersOnFile() { + val file = createTempFile("temp", ".file") assertTrue(file.exists()) assertFalse(file.notExists()) assertTrue(file.isRegularFile()) @@ -147,17 +276,18 @@ class PathExtensionsTest { assertFalse(file.isSymbolicLink()) assertTrue(file.isReadable()) assertTrue(file.isWritable()) - assertTrue(file.isSameFile(file)) + assertTrue(file.isSameFileAs(file)) // The default value of these depends on the current operating system, so just check that // they don't throw an exception. file.isExecutable() file.isHidden() + file.deleteExisting() } @Test - fun testAttributeGettersOnDirectory() { - val file = Files.createTempDirectory(null) + fun attributeGettersOnDirectory() { + val file = createTempDirectory(".tmpdir") assertTrue(file.exists()) assertFalse(file.notExists()) assertFalse(file.isRegularFile()) @@ -165,15 +295,16 @@ class PathExtensionsTest { assertFalse(file.isSymbolicLink()) assertTrue(file.isReadable()) assertTrue(file.isWritable()) - assertTrue(file.isSameFile(file)) + assertTrue(file.isSameFileAs(file)) file.isExecutable() file.isHidden() + file.deleteExisting() } @Test - fun testAttributeGettersOnNonExistentPath() { - val file = Files.createTempDirectory(null).resolve("foo") + fun attributeGettersOnNonExistentPath() { + val file = createTempDirectory().resolve("foo") assertFalse(file.exists()) assertTrue(file.notExists()) assertFalse(file.isRegularFile()) @@ -181,7 +312,7 @@ class PathExtensionsTest { assertFalse(file.isSymbolicLink()) assertFalse(file.isReadable()) assertFalse(file.isWritable()) - assertTrue(file.isSameFile(file)) + assertTrue(file.isSameFileAs(file)) file.isExecutable() // This function will either throw an exception or return false, @@ -190,15 +321,95 @@ class PathExtensionsTest { assertFalse(file.isHidden()) } catch (e: IOException) { } + file.parent.deleteExisting() + } + + private interface SpecialFileAttributesView : FileAttributeView + private interface SpecialFileAttributes : BasicFileAttributes + + @Test + fun readWriteAttributes() { + val file = createTempFile() + val modifiedTime = file.getLastModifiedTime() + assertEquals(modifiedTime, file.getAttribute("lastModifiedTime")) + assertEquals(modifiedTime, file.getAttribute("basic:lastModifiedTime")) + assertEquals(modifiedTime, file.readAttributes().lastModifiedTime()) + assertEquals(modifiedTime, file.readAttributes("basic:lastModifiedTime,creationTime")["lastModifiedTime"]) + assertEquals(modifiedTime, file.readAttributes("*")["lastModifiedTime"]) + + assertFailsWith { file.readAttributes() } + assertFailsWith { file.readAttributes("really_unsupported_view:*") } + assertFailsWith { file.readAttributes("basic:really_unknown_attribute") } + + val newTime1 = FileTime.fromMillis(modifiedTime.toMillis() + 3600_000) + file.setLastModifiedTime(newTime1) + assertEquals(newTime1, file.getLastModifiedTime()) + + val newTime2 = FileTime.fromMillis(modifiedTime.toMillis() + 2 * 3600_000) + file.setAttribute("lastModifiedTime", newTime2) + assertEquals(newTime2, file.getLastModifiedTime()) + + val newTime3 = FileTime.fromMillis(modifiedTime.toMillis() + 3 * 3600_000) + file.fileAttributesView().setTimes(newTime3, null, null) + assertEquals(newTime3, file.getLastModifiedTime()) + + assertFailsWith { file.fileAttributesView() } + assertNull(file.fileAttributesViewOrNull()) + + file.setAttribute("lastModifiedTime", null) + assertEquals(newTime3, file.getLastModifiedTime()) + + file.deleteExisting() } @Test - fun testListDirectoryEntries() { - val dir = Files.createTempDirectory(null) + fun links() { + val dir = createTempDirectory() + val original = createTempFile(dir) + original.writeBytes(Random.nextBytes(100)) + + val link = try { + (dir / ("link-" + original.fileName)).createLinkPointingTo(original) + } catch (e: IOException) { + // may require a privilege + println("Creating a link failed with ${e.stackTraceToString()}") + return + } + + assertTrue(link.isRegularFile()) + assertTrue(link.isRegularFile(LinkOption.NOFOLLOW_LINKS)) + assertTrue(original.isSameFileAs(link)) + compareFiles(original, link) + assertFailsWith { link.readSymbolicLink() } + } + + @Test + fun symlinks() { + val dir = createTempDirectory() + val original = createTempFile(dir) + original.writeBytes(Random.nextBytes(100)) + + val symlink = try { + (dir / ("symlink-" + original.fileName)).createSymbolicLinkPointingTo(original) + } catch (e: IOException) { + // may require a privilege + println("Creating a symlink failed with ${e.stackTraceToString()}") + return + } + + assertTrue(symlink.isRegularFile()) + assertFalse(symlink.isRegularFile(LinkOption.NOFOLLOW_LINKS)) + assertTrue(original.isSameFileAs(symlink)) + compareFiles(original, symlink) + assertEquals(original, symlink.readSymbolicLink()) + } + + @Test + fun directoryEntriesList() { + val dir = createTempDirectory() assertEquals(0, dir.listDirectoryEntries().size) - val file = dir.resolve("f1") - Files.createFile(file) + val file = dir.resolve("f1").createFile() assertEquals(listOf(file), dir.listDirectoryEntries()) val fileTxt = createTempFile(dir, suffix = ".txt") @@ -208,12 +419,11 @@ class PathExtensionsTest { } @Test - fun testUseDirectoryEntries() { - val dir = Files.createTempDirectory(null) + fun directoryEntriesUseSequence() { + val dir = createTempDirectory() assertEquals(0, dir.useDirectoryEntries { it.toList() }.size) - val file = dir.resolve("f1") - Files.createFile(file) + val file = dir.resolve("f1").createFile() assertEquals(listOf(file), dir.useDirectoryEntries { it.toList() }) val fileTxt = createTempFile(dir, suffix = ".txt") @@ -223,12 +433,11 @@ class PathExtensionsTest { } @Test - fun testForEachDirectoryEntry() { - val dir = Files.createTempDirectory(null) + fun directoryEntriesForEach() { + val dir = createTempDirectory() dir.forEachDirectoryEntry { error("shouldn't get here, but received $it") } - val file = dir.resolve("f1") - Files.createFile(file) + val file = createTempFile(dir) dir.forEachDirectoryEntry { assertEquals(file, it) } val fileTxt = createTempFile(dir, suffix = ".txt") @@ -239,9 +448,9 @@ class PathExtensionsTest { private fun testRelativeTo(expected: String?, path: String, base: String) = - testRelativeTo(expected?.let { Paths.get(it) }, Paths.get(path), Paths.get(base)) + testRelativeTo(expected?.let { Path(it) }, Path(path), Path(base)) private fun testRelativeTo(expected: String, path: Path, base: Path) = - testRelativeTo(Paths.get(expected), path, base) + testRelativeTo(Path(expected), path, base) private fun testRelativeTo(expected: Path?, path: Path, base: Path) { val context = "path: '$path', base: '$base'" @@ -304,23 +513,23 @@ class PathExtensionsTest { @Test fun relativeToRelative() { - val nested = Paths.get("foo/bar") - val base = Paths.get("foo") + val nested = Path("foo/bar") + val base = Path("foo") testRelativeTo("bar", nested, base) testRelativeTo("..", base, nested) - val empty = Paths.get("") - val current = Paths.get(".") - val parent = Paths.get("..") - val outOfRoot = Paths.get("../bar") + val empty = Path("") + val current = Path(".") + val parent = Path("..") + val outOfRoot = Path("../bar") testRelativeTo("../bar", outOfRoot, empty) testRelativeTo("../../bar", outOfRoot, base) testRelativeTo("bar", outOfRoot, parent) testRelativeTo("..", parent, outOfRoot) - val root = Paths.get("/root") + val root = Path("/root") val files = listOf(nested, base, empty, outOfRoot, current, parent) val bases = listOf(nested, base, empty, current) @@ -340,10 +549,10 @@ class PathExtensionsTest { @Test fun relativeToFails() { - val absolute = Paths.get("/foo/bar/baz") - val relative = Paths.get("foo/bar") - val networkShare1 = Paths.get("""\\my.host\share1/folder""") - val networkShare2 = Paths.get("""\\my.host\share2\folder""") + val absolute = Path("/foo/bar/baz") + val relative = Path("foo/bar") + val networkShare1 = Path("""\\my.host\share1/folder""") + val networkShare2 = Path("""\\my.host\share2\folder""") val allFiles = listOf(absolute, relative) + if (isBackslashSeparator) listOf(networkShare1, networkShare2) else emptyList() for (file in allFiles) { diff --git a/libraries/stdlib/jdk7/test/PathReadWriteTest.kt b/libraries/stdlib/jdk7/test/PathReadWriteTest.kt index 87c6a91923f..058dcc87baf 100644 --- a/libraries/stdlib/jdk7/test/PathReadWriteTest.kt +++ b/libraries/stdlib/jdk7/test/PathReadWriteTest.kt @@ -8,24 +8,25 @@ package kotlin.jdk7.test import java.nio.file.Files import java.nio.file.StandardOpenOption import kotlin.io.path.* +import kotlin.random.Random import kotlin.test.* class PathReadWriteTest { @Test - fun testAppendText() { - val file = Files.createTempFile(null, null) + fun appendText() { + val file = createTempFile() file.writeText("Hello\n") file.appendText("World\n") file.writeText("Again", Charsets.US_ASCII, StandardOpenOption.APPEND) assertEquals("Hello\nWorld\nAgain", file.readText()) assertEquals(listOf("Hello", "World", "Again"), file.readLines(Charsets.UTF_8)) - file.toFile().deleteOnExit() + file.deleteExisting() } @Test fun file() { - val file = Files.createTempFile(null, null) + val file = createTempFile() val writer = file.outputStream().writer().buffered() writer.write("Hello") @@ -72,31 +73,54 @@ class PathReadWriteTest { } @Test - fun testBufferedReader() { - val file = Files.createTempFile(null, null) + fun bufferedReader() { + val file = createTempFile() val lines = listOf("line1", "line2") - Files.write(file, lines, Charsets.UTF_8) + file.writeLines(lines) - assertEquals(file.bufferedReader().use { it.readLines() }, lines) - assertEquals(file.bufferedReader(Charsets.UTF_8, 1024, StandardOpenOption.READ).use { it.readLines() }, lines) + assertEquals(lines, file.bufferedReader().use { it.readLines() }) + assertEquals(lines, file.bufferedReader(Charsets.UTF_8, 1024, StandardOpenOption.READ).use { it.readLines() }) } @Test - fun testBufferedWriter() { - val file = Files.createTempFile(null, null) + fun bufferedWriter() { + val file = createTempFile() file.bufferedWriter().use { it.write("line1\n") } file.bufferedWriter(Charsets.UTF_8, 1024, StandardOpenOption.APPEND).use { it.write("line2\n") } - assertEquals(Files.readAllLines(file, Charsets.UTF_8), listOf("line1", "line2")) + assertEquals(listOf("line1", "line2"), file.readLines()) } @Test - fun testWriteBytes() { - val file = Files.createTempFile(null, null) + fun writeBytes() { + val file = createTempFile() file.writeBytes("Hello".encodeToByteArray()) file.appendBytes(" world!".encodeToByteArray()) assertEquals(file.readText(), "Hello world!") + + val bytes = Random.nextBytes(100) + file.writeBytes(bytes) + file.appendBytes(bytes) + assertTrue((bytes + bytes) contentEquals file.readBytes()) + } + + @Test + fun writeLines() { + val file = createTempFile() + val lines = listOf("first line", "second line") + file.writeLines(lines) + assertEquals(lines, file.readLines()) + + file.writeLines(lines.asSequence()) + assertEquals(lines, file.readLines()) + + val moreLines = listOf("third line", "the bottom line") + file.appendLines(moreLines) + assertEquals(lines + moreLines, file.readLines()) + + file.appendLines(moreLines.asSequence()) + assertEquals(lines + moreLines + moreLines, file.readLines()) } } diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-jdk7.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-jdk7.txt index c77b8fc031c..8d3f61a1aa4 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-jdk7.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-jdk7.txt @@ -1,3 +1,23 @@ +public abstract interface annotation class kotlin/io/path/ExperimentalPathApi : java/lang/annotation/Annotation { +} + +public final class kotlin/io/path/PathsKt { + public static final fun appendText (Ljava/nio/file/Path;Ljava/lang/String;Ljava/nio/charset/Charset;)V + public static synthetic fun appendText$default (Ljava/nio/file/Path;Ljava/lang/String;Ljava/nio/charset/Charset;ILjava/lang/Object;)V + public static final fun fileAttributeViewNotAvailable (Ljava/nio/file/Path;Ljava/lang/Class;)Ljava/lang/Void; + public static final fun getExtension (Ljava/nio/file/Path;)Ljava/lang/String; + public static final fun getInvariantSeparatorsPath (Ljava/nio/file/Path;)Ljava/lang/String; + public static final fun getName (Ljava/nio/file/Path;)Ljava/lang/String; + public static final fun getNameWithoutExtension (Ljava/nio/file/Path;)Ljava/lang/String; + public static final fun listDirectoryEntries (Ljava/nio/file/Path;Ljava/lang/String;)Ljava/util/List; + public static synthetic fun listDirectoryEntries$default (Ljava/nio/file/Path;Ljava/lang/String;ILjava/lang/Object;)Ljava/util/List; + 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 relativeToOrSelf (Ljava/nio/file/Path;Ljava/nio/file/Path;)Ljava/nio/file/Path; + public static final fun writeText (Ljava/nio/file/Path;Ljava/lang/String;Ljava/nio/charset/Charset;[Ljava/nio/file/OpenOption;)V + public static synthetic fun writeText$default (Ljava/nio/file/Path;Ljava/lang/String;Ljava/nio/charset/Charset;[Ljava/nio/file/OpenOption;ILjava/lang/Object;)V +} + public final class kotlin/jdk7/AutoCloseableKt { public static final fun closeFinally (Ljava/lang/AutoCloseable;Ljava/lang/Throwable;)V }