Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package io.kform
import kotlin.js.JsName
import kotlin.jvm.JvmField
import kotlin.jvm.JvmOverloads
import kotlin.math.min
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* Representation of an absolute path as a subtype of [Path] and as a list of absolute path
* fragments. Absolute paths are always "resolved" (i.e. they contain no unnecessary fragments such
* as current path fragments or consecutive recursive wildcard fragments).
*
* Absolute paths represent locations of data from a root location: e.g. the location of a value
* within a value where the outer value is seen as the "root" value.
*
* Unlike regular paths, the root fragment is always implicit in absolute paths and is not visible
* in the list of fragments.
*/
@JsName("AbsolutePathKt")
@Serializable(with = AbsolutePath.Serializer::class)
public class AbsolutePath
private constructor(fragments: List, needsResolving: Boolean) :
Path(if (needsResolving) resolveImpl(listOf(PathFragment.Root), fragments) else fragments) {
// The [realFragments] have a [PathFragment.RootFragment] at index `0`
@Suppress("UNCHECKED_CAST")
override val fragments: List
get() = realFragments.drop(1) as List
/** Size of the path (number of fragments it has). */
public val size: Int
get() = realFragments.size - 1
/** Whether this path represents the root path. */
public val isRoot: Boolean
get() = realFragments.size == 1
/** Last fragment of this path or `null` for the root path. */
public val lastFragment: AbsolutePathFragment?
get() = if (isRoot) null else realFragments.last() as AbsolutePathFragment
/** Creates an absolute path from a list of [fragments]. */
@JvmOverloads
public constructor(
fragments: List = emptyList()
) : this(
// Remove consecutive recursive wildcard fragments
listOf(PathFragment.Root) +
fragments.filterIndexed { i, fragment ->
fragment != AbsolutePathFragment.RecursiveWildcard ||
fragments.getOrNull(i + 1) != AbsolutePathFragment.RecursiveWildcard
},
needsResolving = false
)
/**
* Creates an absolute path from a path [path]. The path will be resolved against the root path.
*/
public constructor(
path: Path
) : this(path.realFragments, needsResolving = path !is AbsolutePath)
/**
* Creates an absolute path from a string representation of a path [stringPath]. The string will
* first be converted into a path and then resolved against the root path.
*/
public constructor(stringPath: String) : this(Path(stringPath))
/**
* Returns the fragment of this path with index [index].
*
* @throws IndexOutOfBoundsException When an out of bounds [index] is provided.
*/
public override fun fragment(index: Int): AbsolutePathFragment =
realFragments[index + 1] as AbsolutePathFragment // Skip root fragment
/**
* Returns the fragment of this path with index [index].
*
* @throws IndexOutOfBoundsException When an out of bounds [index] is provided.
*/
public override operator fun get(index: Int): AbsolutePathFragment = fragment(index)
/** Returns whether this path has at least one non-recursive wildcard. */
public fun hasWildcard(): Boolean =
realFragments.any { fragment -> fragment is AbsolutePathFragment.Wildcard }
/** Returns whether this path has at least one recursive wildcard. */
public fun hasRecursiveWildcard(): Boolean =
realFragments.any { fragment -> fragment is AbsolutePathFragment.RecursiveWildcard }
/** Returns whether this path has at least one wildcard (either recursive or not). */
public fun hasAnyWildcard(): Boolean =
realFragments.any { fragment ->
fragment is AbsolutePathFragment.Wildcard ||
fragment is AbsolutePathFragment.RecursiveWildcard
}
/**
* Returns an absolute path representing the parent of this path. Equivalent to `resolve("..")`.
*
* Note that the parent of the root path is the root path itself.
*/
override fun parent(): AbsolutePath =
AbsolutePath(parentImpl(realFragments), needsResolving = false)
/** Returns the absolute path resulting from appending [fragments] to this path. */
public fun append(vararg fragments: AbsolutePathFragment): AbsolutePath =
AbsolutePath(
// Remove consecutive recursive wildcard fragments
realFragments +
fragments.filterIndexed { i, fragment ->
fragment != AbsolutePathFragment.RecursiveWildcard ||
(if (i == 0) realFragments.last() else fragments[i - 1]) !=
AbsolutePathFragment.RecursiveWildcard
},
needsResolving = false
)
/** Returns the absolute path resulting from appending [fragment] to this path. */
public operator fun plus(fragment: AbsolutePathFragment): AbsolutePath = append(fragment)
/** Returns the absolute path resulting from resolving a list of [paths] against this path. */
override fun resolve(vararg paths: Path): AbsolutePath =
when {
paths.isEmpty() -> this
// Optimise case where last path (in most cases the only path) is an absolute path, in
// which case no resolving is needed
paths.last() is AbsolutePath -> paths.last() as AbsolutePath
else ->
AbsolutePath(
resolveImpl(
realFragments,
*paths.map { path -> path.realFragments }.toTypedArray()
),
needsResolving = false
)
}
/**
* Returns the absolute path resulting from resolving a list of paths in string notation
* [stringPaths] against this path.
*/
override fun resolve(vararg stringPaths: String): AbsolutePath =
resolve(*stringPaths.map { str -> Path(str) }.toTypedArray())
/**
* Returns the absolute path resulting from resolving [path] against this path. Equivalent to
* `resolve(path)`.
*/
public override operator fun plus(path: Path): AbsolutePath = resolve(path)
/**
* Returns the absolute path resulting from resolving the path in string notation [stringPath]
* against this path. Equivalent to `resolve(stringPath)`.
*/
public override operator fun plus(stringPath: String): AbsolutePath = resolve(stringPath)
/**
* Returns whether this path matches with [path]. Note that [path] will be converted to an
* [absolute path][AbsolutePath] when it isn't one.
*
* For the purpose of matching, a [wildcard fragment][AbsolutePathFragment.Wildcard] matches
* against any other fragment and a [recursive wildcard fragment]
* [AbsolutePathFragment.RecursiveWildcard] matches against zero or more any other fragments.
* Non-wildcard fragments match against each other on a basis of equality (`==`).
*/
public fun matches(path: Path): Boolean =
matchesImpl(fragments, path.toAbsolutePath().fragments)
/**
* Returns whether this path contains [path]. Note that [path] will be converted to an
* [absolute path][AbsolutePath] when it isn't one.
*
* We say that a path `p1` contains a path `p2` when all paths that [match][matches] against
* `p2` also [match][matches] against `p1`. I.e. `p1.contains(p2) == true` iff there does
* **not** exist a path `p3` such that `p3.matches(p2) == true` and `p3.matches(p1) == false`.
*/
public operator fun contains(path: Path): Boolean =
containsImpl(fragments, path.toAbsolutePath().fragments)
/**
* Returns a relative path representing this path, but relative to [path]. Note that [path] will
* be converted to an [absolute path][AbsolutePath] when it isn't one.
*
* In other words, if `relPath1 = path1.relativeTo(path2)`, then `path2.resolve(relPath1) ==
* path1`.
*/
public fun relativeTo(path: Path): Path =
Path(relativeImpl(path.toAbsolutePath().fragments, fragments))
override fun equals(other: Any?): Boolean =
when {
this === other -> true
other !is Path -> false
other is AbsolutePath -> realFragments == other.realFragments
else -> realFragments == other.resolve().realFragments
}
override fun hashCode(): Int = realFragments.hashCode()
override operator fun iterator(): Iterator = fragments.iterator()
public companion object {
// Common paths:
/** Absolute root path (`"/"`). */
@JvmField public val ROOT: AbsolutePath = AbsolutePath()
/** Absolute path that matches all other paths (`"/∗∗"`). */
@JvmField
public val MATCH_ALL: AbsolutePath =
AbsolutePath(listOf(AbsolutePathFragment.RecursiveWildcard))
/**
* Implementation of [matches]. Returns whether two lists of fragments match.
*
* The algorithm recursively matches the two lists taking into consideration recursive
* wildcard fragments. It implements the following pseudo-algorithm:
* ```
* matches l1 l2 = match (l1, l2):
* | ([], []) -> true
* | ([**], []) -> true
* | ([], [**]) -> true
* | (**::t1, h2::t2) -> matches(t1, h2::t2) || matches(**::t1, t2)
* | (h1::t1, **::t2) -> matches(h1::t1, t2) || matches(t1, **::t2)
* | (*::t1, _::t2) -> matches(t1, t2)
* | (_::t1, *::t2) -> matches(t1, t2)
* | (h::t1, h::t2) -> matches(t1, t2)
* | _ -> false
* ```
*
* @param f1 First list of fragments.
* @param f2 Second list of fragments.
* @param i1 Index of `f1`'s head. `l1` in the above pseudocode is represented by
* `f1.slice(i1)`.
* @param i2 Index of `f2`'s head. `l2` in the above pseudocode is represented by
* `f2.slice(i2)`.
*/
private fun matchesImpl(
f1: List,
f2: List,
i1: Int = 0,
i2: Int = 0
): Boolean {
// List sizes
val s1 = f1.size - i1
val s2 = f2.size - i2
// List heads
val h1 = f1.getOrNull(i1)
val h2 = f2.getOrNull(i2)
// Base cases:
// | ([], []) -> true
// | ([**], []) -> true
// | ([], [**]) -> true
if (
(s1 == 0 && s2 == 0) ||
(s1 == 1 && s2 == 0 && h1 is AbsolutePathFragment.RecursiveWildcard) ||
(s1 == 0 && s2 == 1 && h2 is AbsolutePathFragment.RecursiveWildcard)
) {
return true
}
// Cases where both lists have at least 1 element:
if (s1 > 0 && s2 > 0) {
// Recursive wildcard cases:
// | (**::t1, h2::t2) -> matches(t1, h2::t2) || matches(**::t1, t2)
// | (h1::t1, **::t2) -> matches(h1::t1, t2) || matches(t1, **::t2)
if (
h1 is AbsolutePathFragment.RecursiveWildcard ||
h2 is AbsolutePathFragment.RecursiveWildcard
) {
return matchesImpl(f1, f2, i1 + 1, i2) || matchesImpl(f1, f2, i1, i2 + 1)
}
// Other cases:
// | (*::t1, _::t2) -> matches(t1, t2)
// | (_::t1, *::t2) -> matches(t1, t2)
// | (h::t1, h::t2) -> matches(t1, t2)
if (
h1 is AbsolutePathFragment.Wildcard ||
h2 is AbsolutePathFragment.Wildcard ||
h1 == h2
) {
return matchesImpl(f1, f2, i1 + 1, i2 + 1)
}
}
// _ -> false
return false
}
/**
* Implementation of [contains]. Returns whether the first list of fragments contains the
* second one.
*
* The algorithm recursively checks whether one list contains the other taking into
* consideration recursive wildcard fragments. It implements the following pseudo-algorithm:
* ```
* contains l1 l2 = match (l1, l2):
* | ([], []) -> true
* | ([**], _) -> true
* | (**::t1, h2::t2) -> contains(t1, h2::t2) || contains(**::t1, t2)
* | (*::t1, h2::t2) -> h2 != ** && contains(t1, t2)
* | (h::t1, h::t2) -> contains(t1, t2)
* | _ -> false
* ```
*
* @param f1 First list of fragments.
* @param f2 Second list of fragments.
* @param i1 Index of `f1`'s head. `l1` in the above pseudocode is represented by
* `f1.slice(i1)`.
* @param i2 Index of `f2`'s head. `l2` in the above pseudocode is represented by
* `f2.slice(i2)`.
*/
private fun containsImpl(
f1: List,
f2: List,
i1: Int = 0,
i2: Int = 0
): Boolean {
// List sizes
val s1 = f1.size - i1
val s2 = f2.size - i2
// List heads
val h1 = f1.getOrNull(i1)
val h2 = f2.getOrNull(i2)
// Base cases:
// | ([], []) -> true
// | ([**], _) -> true
if ((s1 == 0 && s2 == 0) || (s1 == 1 && h1 is AbsolutePathFragment.RecursiveWildcard)) {
return true
}
// Cases where both lists have at least 1 element:
if (s1 > 0 && s2 > 0) {
// Recursive wildcard case:
// | (**::t1, h2::t2) -> contains(**::t1, t2) || contains(t1, h2::t2)
if (h1 is AbsolutePathFragment.RecursiveWildcard) {
return containsImpl(f1, f2, i1 + 1, i2) || containsImpl(f1, f2, i1, i2 + 1)
}
// Other cases:
// | (*::t1, h2::t2) -> h2 != ** && contains(t1, t2)
// | (h::t1, h::t2) -> h != - && contains(t1, t2)
if (
(h1 is AbsolutePathFragment.Wildcard &&
h2 !is AbsolutePathFragment.RecursiveWildcard) || h1 == h2
) {
return containsImpl(f1, f2, i1 + 1, i2 + 1)
}
}
// _ -> false
return false
}
}
/** Returns the relative path from [from] to [to]. Used to implement [relativeTo]. */
private fun relativeImpl(
from: List,
to: List
): List {
val result = mutableListOf()
val size = min(from.size, to.size)
var samePartsSize = size
for (i in 0 until size) {
if (from[i] != to[i]) {
samePartsSize = i
break
}
}
for (i in samePartsSize until from.size) {
result += PathFragment.ParentPath
}
result += (to.slice(samePartsSize until to.size))
return result
}
/** Absolute path serialiser, serialising absolute paths as strings. */
public object Serializer : KSerializer {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("io.kform.AbsolutePath", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: AbsolutePath): Unit =
encoder.encodeString(value.toString())
override fun deserialize(decoder: Decoder): AbsolutePath =
AbsolutePath(decoder.decodeString())
}
}