Add Base37 implementation

Graham 2 years ago
parent 0c2108d750
commit 0666df686c
  1. 89
      util/src/main/kotlin/org/openrs2/util/Base37.kt
  2. 120
      util/src/test/kotlin/org/openrs2/util/Base37Test.kt

@ -0,0 +1,89 @@
package org.openrs2.util
public object Base37 {
private const val MAX_LENGTH: Int = 12
private val FIRST_VALID_NAME = encode("")
private val LAST_VALID_NAME = encode("999999999999")
private val DECODE_TABLE = CharArray(37) { i ->
when (i) {
0 -> '_'
in 1..26 -> 'a' + (i - 1)
else -> '0' + (i - 27)
}
}
public fun encode(s: String): Long {
// casting to CharSequence avoids a copy
val trimmed = (s as CharSequence).trim { it == ' ' || it == '_' }
require(trimmed.length <= MAX_LENGTH)
var n = 0L
for (c in trimmed) {
n *= 37
when (c) {
in 'A'..'Z' -> n += 1 + (c - 'A')
in 'a'..'z' -> n += 1 + (c - 'a')
in '0'..'9' -> n += 27 + (c - '0')
' ', '_' -> Unit
else -> throw IllegalArgumentException()
}
}
return n
}
public fun decodeLowerCase(n: Long): String {
require(n in FIRST_VALID_NAME..LAST_VALID_NAME)
require(n == 0L || n % 37 != 0L)
val chars = CharArray(MAX_LENGTH)
var len = 0
var temp = n
while (temp != 0L) {
chars[len++] = DECODE_TABLE[(temp % 37).toInt()]
temp /= 37
}
chars.reverse(0, len)
return String(chars, 0, len)
}
public fun decodeTitleCase(n: Long): String {
require(n in FIRST_VALID_NAME..LAST_VALID_NAME)
require(n == 0L || n % 37 != 0L)
val chars = CharArray(MAX_LENGTH)
var len = 0
var temp = n
while (temp != 0L) {
var c = DECODE_TABLE[(temp % 37).toInt()]
temp /= 37
if (c == '_') {
c = ' '
chars[len - 1] = chars[len - 1].uppercaseChar()
}
chars[len++] = c
}
chars.reverse(0, len)
chars[0] = chars[0].uppercaseChar()
return String(chars, 0, len)
}
public fun toLowerCase(s: String): String {
return decodeLowerCase(encode(s))
}
public fun toTitleCase(s: String): String {
return decodeTitleCase(encode(s))
}
}

@ -0,0 +1,120 @@
package org.openrs2.util
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class Base37Test {
@Test
fun testEncodeBounds() {
assertEquals(0, Base37.encode(""))
assertEquals(1, Base37.encode("a"))
assertEquals(1, Base37.encode("A"))
assertEquals(6582952005840035279, Base37.encode("999999999998"))
assertEquals(6582952005840035280, Base37.encode("999999999999"))
assertFailsWith<IllegalArgumentException> {
Base37.encode("aaaaaaaaaaaaa")
}
}
@Test
fun testEncodeBoundsWhitespace() {
assertEquals(6582952005840035280, Base37.encode("999999999999 "))
assertEquals(6582952005840035280, Base37.encode(" 999999999999"))
assertEquals(6582952005840035280, Base37.encode(" 999999999999 "))
assertFailsWith<IllegalArgumentException> {
Base37.encode("aaaaaaaaaaaaa ")
}
assertFailsWith<IllegalArgumentException> {
Base37.encode(" aaaaaaaaaaaaa")
}
assertFailsWith<IllegalArgumentException> {
Base37.encode(" aaaaaaaaaaaaa ")
}
}
@Test
fun testDecodeLowerCaseBounds() {
assertFailsWith<IllegalArgumentException> {
Base37.decodeLowerCase(-1)
}
assertEquals("", Base37.decodeLowerCase(0))
assertEquals("a", Base37.decodeLowerCase(1))
assertEquals("999999999998", Base37.decodeLowerCase(6582952005840035279))
assertEquals("999999999999", Base37.decodeLowerCase(6582952005840035280))
assertFailsWith<IllegalArgumentException> {
Base37.decodeLowerCase(6582952005840035281)
}
}
@Test
fun testDecodeTitleCaseBounds() {
assertFailsWith<IllegalArgumentException> {
Base37.decodeTitleCase(-1)
}
assertEquals("", Base37.decodeTitleCase(0))
assertEquals("A", Base37.decodeTitleCase(1))
assertEquals("999999999998", Base37.decodeTitleCase(6582952005840035279))
assertEquals("999999999999", Base37.decodeTitleCase(6582952005840035280))
assertFailsWith<IllegalArgumentException> {
Base37.decodeTitleCase(6582952005840035281)
}
}
@Test
fun testEncodeInvalidChar() {
assertFailsWith<IllegalArgumentException> {
Base37.encode("!")
}
}
@Test
fun testEncodeWhitespace() {
assertEquals(1465402762952, Base37.encode("Open Rs2"))
assertEquals(1465402762952, Base37.encode("open_rs2"))
assertEquals(1465402762952, Base37.encode(" Open Rs2 "))
assertEquals(1465402762952, Base37.encode("_Open Rs2_"))
}
@Test
fun testDecodeLowerCaseWhitespace() {
assertEquals("open_rs2", Base37.decodeLowerCase(1465402762952))
}
@Test
fun testDecodeTitleCaseWhitespace() {
assertEquals("Open Rs2", Base37.decodeTitleCase(1465402762952))
}
@Test
fun testDecodeLowerCaseTrailingWhitespace() {
assertFailsWith<IllegalArgumentException> {
Base37.decodeLowerCase(54219902229224)
}
}
@Test
fun testDecodeTitleCaseTrailingWhitespace() {
assertFailsWith<IllegalArgumentException> {
Base37.decodeTitleCase(54219902229224)
}
}
@Test
fun testToLowerCase() {
assertEquals("open_rs2", Base37.toLowerCase(" OpEn rS2_"))
}
@Test
fun testToTitleCase() {
assertEquals("Open Rs2", Base37.toTitleCase(" OpEn rS2_"))
}
}
Loading…
Cancel
Save