Shell.kt
package de.pflugradts.passbird.domain.model.shell
import de.pflugradts.passbird.domain.model.ddd.ValueObject
import de.pflugradts.passbird.domain.model.shell.PlainShell.Companion.plainShellOf
import java.security.SecureRandom
import java.util.stream.Stream
import java.util.stream.StreamSupport
class Shell private constructor(
private val content: ByteArray,
) : ValueObject,
Iterable<Byte> {
val size get() = content.size
val isEmpty get() = size == 0
val isNotEmpty get() = !isEmpty
val firstByte get() = content[0]
fun getByte(index: Int) = content[index]
fun getChar(index: Int) = Char(getByte(index).toUShort())
override fun iterator() = ShellIterator(content)
fun copy() = shellOf(content.clone())
fun stream(): Stream<Byte> = StreamSupport.stream(spliterator(), false)
fun toByteArray() = content.clone()
operator fun plus(other: Shell) = shellOf(content + other.toByteArray())
fun toPlainShell(): PlainShell {
val c = CharArray(size)
for (i in 0 until size) c[i] = Char(content[i].toUShort())
return plainShellOf(c)
}
fun asString(): String {
val builder = StringBuilder()
for (i in 0 until size) {
builder.append(Char(content[i].toUShort()))
}
return builder.toString()
}
@JvmOverloads
fun slice(fromInclusive: Int, toExclusive: Int = content.size): Shell = if (toExclusive - fromInclusive > 0) {
val sub = ByteArray(toExclusive - fromInclusive)
System.arraycopy(content, fromInclusive, sub, 0, sub.size)
shellOf(sub)
} else {
emptyShell()
}
fun scramble() = content.indices.forEach {
content[it] = randomAsciiByteExcept(content[it])
}
private fun randomAsciiByteExcept(byte: Byte): Byte {
var randomByte = randomAsciiByte()
while (randomByte == byte) randomByte = randomAsciiByte()
return randomByte
}
private fun randomAsciiByte() = (SECURE_RANDOM.nextInt(1 + MAX_ASCII_VALUE - MIN_ASCII_VALUE) + MIN_ASCII_VALUE).toByte()
class ShellIterator(private val content: ByteArray) : Iterator<Byte> {
private var index = 0
override fun hasNext() = index < content.size
override fun next() = if (hasNext()) content[index++] else throw NoSuchElementException()
}
override fun equals(other: Any?): Boolean = when {
(this === other) -> true
(javaClass != other?.javaClass) -> false
else -> content contentEquals (other as Shell).content
}
override fun hashCode() = content.contentHashCode()
companion object {
private val SECURE_RANDOM = SecureRandom()
@JvmStatic
fun shellOf(bytes: ByteArray) = Shell(bytes.clone())
@JvmStatic
fun shellOf(b: List<Byte>): Shell {
val bytes = ByteArray(b.size)
for (i in b.indices) {
bytes[i] = b[i]
}
return shellOf(bytes)
}
@JvmStatic
fun shellOf(s: String): Shell = plainShellOf(s.toCharArray()).toShell()
@JvmStatic
fun emptyShell(): Shell = shellOf(ByteArray(0))
}
}
typealias ShellPair = Pair<Shell, Shell>