132 lines
5.2 KiB
Kotlin
132 lines
5.2 KiB
Kotlin
package kotlin.io
|
|
|
|
import java.io.File
|
|
import java.util.Collections
|
|
import java.util.NoSuchElementException
|
|
import kotlin.text.Regex
|
|
|
|
/**
|
|
* Estimation of a root name by a given file name.
|
|
*
|
|
* This implementation is able to find /, Drive:/, Drive: or
|
|
* //network.name/root as possible root names.
|
|
* / denotes File.separator here so \ can be used instead.
|
|
* All other possible roots cannot be identified by this implementation.
|
|
* It's also not guaranteed (but possible) that function will be able to detect a root
|
|
* which is incorrect for current OS. For instance, in Unix function cannot detect
|
|
* network root names like //network.name/root, but can detect Windows roots like C:/.
|
|
*
|
|
* @return string representing the root for this file, or empty string is this file name is relative.
|
|
*/
|
|
private fun String.getRootName(): String {
|
|
// Note: separators should be already replaced to system ones
|
|
var first = indexOf(File.separatorChar, 0)
|
|
if (first == 0) {
|
|
if (length() > 1 && this[1] == File.separatorChar) {
|
|
// Network names like //my.host/home/something ? => //my.host/home/ should be root
|
|
// NB: does not work in Unix because //my.host/home is converted into /my.host/home there
|
|
// So in Windows we'll have root of //my.host/home but in Unix just /
|
|
first = indexOf(File.separatorChar, 2)
|
|
if (first >= 0) {
|
|
val dot = indexOf('.', 2)
|
|
if (dot >= 0 && dot < first) {
|
|
first = indexOf(File.separatorChar, first + 1)
|
|
if (first >= 0)
|
|
return substring(0, first + 1)
|
|
}
|
|
}
|
|
}
|
|
return substring(0, 1)
|
|
}
|
|
// C:\
|
|
if (first > 0 && this[first - 1] == ':') {
|
|
first++
|
|
return substring(0, first)
|
|
}
|
|
// C:
|
|
if (first == -1 && endsWith(':'))
|
|
return this
|
|
return ""
|
|
|
|
}
|
|
|
|
/**
|
|
* Estimation of a root name for this file.
|
|
*
|
|
* This implementation is able to find /, Drive:/, Drive: or
|
|
* //network.name/root as possible root names.
|
|
* / denotes File.separator here so \ can be used instead.
|
|
* All other possible roots cannot be identified by this implementation.
|
|
* It's also not guaranteed (but possible) that function will be able to detect a root
|
|
* which is incorrect for current OS. For instance, in Unix function cannot detect
|
|
* network root names like //network.name/root, but can detect Windows roots like C:/.
|
|
*
|
|
* @return string representing the root for this file, or empty string is this file name is relative.
|
|
*/
|
|
public val File.rootName: String
|
|
get() = separatorsToSystem().getRootName()
|
|
|
|
/**
|
|
* Returns root component of this abstract name, like / from /home/user, or C:\ from C:\file.tmp,
|
|
* or //my.host/home for //my.host/home/user,
|
|
* or `null` if this name is relative, like bar/gav
|
|
*/
|
|
public val File.root: File?
|
|
get() {
|
|
val name = rootName
|
|
return if (name.length() > 0) File(name) else null
|
|
}
|
|
|
|
/**
|
|
* Represents the path to a file as a collection of directories.
|
|
*
|
|
* @property rootName the name of the root of the path (for example, `/` or `C:`).
|
|
* @property fileList the list of [File] objects representing every directory in the path to the file,
|
|
* up to an including the file itself.
|
|
*/
|
|
public data class FilePathComponents(public val rootName: String, public val fileList: List<File>) {
|
|
/**
|
|
* Returns the number of elements in the path to the file.
|
|
*/
|
|
public fun size(): Int = fileList.size()
|
|
|
|
/**
|
|
* Returns a sub-path of the path, starting with the directory at the specified [beginIndex] and up
|
|
* to the specified [endIndex].
|
|
*/
|
|
public fun subPath(beginIndex: Int, endIndex: Int): File {
|
|
if (beginIndex < 0 || beginIndex > endIndex || endIndex > size())
|
|
throw IllegalArgumentException()
|
|
|
|
return File(fileList.subList(beginIndex, endIndex).joinToString(File.separator))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Splits the file into path components (the names of containing directories and the name of the file
|
|
* itself) and returns the resulting collection of components.
|
|
*/
|
|
public fun File.filePathComponents(): FilePathComponents {
|
|
val path = separatorsToSystem()
|
|
val rootName = path.getRootName()
|
|
val subPath = path.substring(rootName.length())
|
|
// if: a special case when we have only root component
|
|
// Split not only by / or \, but also by //, ///, \\, \\\, etc.
|
|
val list = if (rootName.length() > 0 && subPath.isEmpty()) listOf() else
|
|
// Looks awful but we split just by /+ or \+ depending on OS
|
|
subPath.split(Regex.fromLiteral(File.separatorChar.toString())).toList().map { it -> File(it) }
|
|
return FilePathComponents(rootName, list)
|
|
}
|
|
|
|
/**
|
|
* Returns a relative pathname which is a subsequence of this pathname,
|
|
* beginning from component [beginIndex], inclusive,
|
|
* ending at component [endIndex], exclusive.
|
|
* Number 0 belongs to a component closest to the root,
|
|
* number count-1 belongs to a component farthest from the root.
|
|
* @throws IllegalArgumentException if [beginIndex] is negative,
|
|
* or [endIndex] is greater than existing number of components,
|
|
* or [beginIndex] is greater than [endIndex].
|
|
*/
|
|
public fun File.subPath(beginIndex: Int, endIndex: Int): File = filePathComponents().subPath(beginIndex, endIndex)
|