DroidFS/app/src/main/java/sushi/hardcore/droidfs/collation/CollatorFileNameExtensions.kt

116 lines
3.9 KiB
Kotlin

/*
* Code borrowed from the awesome Material Files app (https://github.com/zhanghai/MaterialFiles)
*
* Copyright (c) 2020 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/
package sushi.hardcore.droidfs.collation
import java.text.CollationKey
import java.text.Collator
import kotlin.math.min
private val COLLATION_SENTINEL = byteArrayOf(1, 1, 1)
// @see https://github.com/GNOME/glib/blob/mainline/glib/gunicollate.c
// g_utf8_collate_key_for_filename()
fun Collator.getCollationKeyForFileName(source: String): CollationKey {
val result = ByteStringBuilder()
val suffix = ByteStringBuilder()
var previousIndex = 0
var index = 0
val endIndex = source.length
while (index < endIndex) {
when {
source[index] == '.' -> {
if (previousIndex != index) {
val collationKey = getCollationKey(source.substring(previousIndex, index))
result.append(collationKey.toByteArray())
}
result.append(COLLATION_SENTINEL).append(1)
previousIndex = index + 1
}
source[index].isAsciiDigit() -> {
if (previousIndex != index) {
val collationKey = getCollationKey(source.substring(previousIndex, index))
result.append(collationKey.toByteArray())
}
result.append(COLLATION_SENTINEL).append(2)
previousIndex = index
var leadingZeros: Int
var digits: Int
if (source[index] == '0') {
leadingZeros = 1
digits = 0
} else {
leadingZeros = 0
digits = 1
}
while (++index < endIndex) {
if (source[index] == '0' && digits == 0) {
++leadingZeros
} else if (source[index].isAsciiDigit()) {
++digits
} else {
if (digits == 0) {
++digits
--leadingZeros
}
break
}
}
while (digits > 1) {
result.append(':'.code.toByte())
--digits
}
if (leadingZeros > 0) {
suffix.append(leadingZeros.toByte())
previousIndex += leadingZeros
}
result.append(source.substring(previousIndex, index).toByteString())
previousIndex = index
--index
}
else -> {}
}
++index
}
if (previousIndex != index) {
val collationKey = getCollationKey(source.substring(previousIndex, index))
result.append(collationKey.toByteArray())
}
result.append(suffix.toByteString())
return ByteArrayCollationKey(source, result.toByteString().borrowBytes())
}
private fun Char.isAsciiDigit(): Boolean = this in '0'..'9'
private class ByteArrayCollationKey(
@Suppress("CanBeParameter")
private val source: String,
private val bytes: ByteArray
) : CollationKey(source) {
override fun compareTo(other: CollationKey): Int {
other as ByteArrayCollationKey
return bytes.unsignedCompareTo(other.bytes)
}
override fun toByteArray(): ByteArray = bytes.copyOf()
}
private fun ByteArray.unsignedCompareTo(other: ByteArray): Int {
val size = size
val otherSize = other.size
for (index in 0 until min(size, otherSize)) {
val byte = this[index].toInt() and 0xFF
val otherByte = other[index].toInt() and 0xFF
if (byte < otherByte) {
return -1
} else if (byte > otherByte) {
return 1
}
}
return size - otherSize
}