summaryrefslogtreecommitdiff
path: root/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/serializers/ConfigMemorySizeSerializer.kt
blob: 804a3a6c793b67b9efb6888e28894e7466a61356 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package kotlinx.serialization.hocon.serializers

import com.typesafe.config.*
import java.math.BigInteger
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.hocon.*

/**
 * Serializer for [ConfigMemorySize].
 * All possible Hocon size formats [https://github.com/lightbend/config/blob/main/HOCON.md#size-in-bytes-format] are accepted for decoding.
 * During encoding, the serializer emits values using powers of two: byte, KiB, MiB, GiB, TiB, PiB, EiB, ZiB, YiB.
 * Encoding uses the largest possible integer value.
 * Example:
 *  1024 byte -> 1 KiB;
 *  1024 KiB -> 1 MiB;
 *  1025 KiB -> 1025 KiB.
 * Usage example:
 * ```
 * @Serializable
 * data class ConfigMemory(
 *      @Serializable(ConfigMemorySizeSerializer::class)
 *      val size: ConfigMemorySize
 * )
 * val config = ConfigFactory.parseString("size = 1 MiB")
 * val configMemory = Hocon.decodeFromConfig(ConfigMemory.serializer(), config)
 * val newConfig = Hocon.encodeToConfig(ConfigMemory.serializer(), configMemory)
 * ```
 */
@ExperimentalSerializationApi
object ConfigMemorySizeSerializer : KSerializer<ConfigMemorySize> {

    // For powers of two.
    private val memoryUnitFormats = listOf("byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB")

    override val descriptor: SerialDescriptor =
        PrimitiveSerialDescriptor("hocon.com.typesafe.config.ConfigMemorySize", PrimitiveKind.STRING)

    override fun deserialize(decoder: Decoder): ConfigMemorySize =
        if (decoder is HoconDecoder) decoder.decodeConfigValue { conf, path -> conf.decodeMemorySize(path) }
        else throwUnsupportedFormatException("ConfigMemorySizeSerializer")

    override fun serialize(encoder: Encoder, value: ConfigMemorySize) {
        if (encoder is HoconEncoder) {
            // We determine that it is divisible by 1024 (2^10).
            // And if it is divisible, then the number itself is shifted to the right by 10.
            // And so on until we find one that is no longer divisible by 1024.
            // ((n & ((1 << m) - 1)) == 0)
            val andVal = BigInteger.valueOf(1023) // ((2^10) - 1) = 0x3ff = 1023
            var bytes = value.toBytesBigInteger()
            var unitIndex = 0
            while (bytes.and(andVal) == BigInteger.ZERO) { // n & 0x3ff == 0
                if (unitIndex < memoryUnitFormats.lastIndex) {
                    bytes = bytes.shiftRight(10)
                    unitIndex++
                } else break
            }
            encoder.encodeString("$bytes ${memoryUnitFormats[unitIndex]}")
        } else {
            throwUnsupportedFormatException("ConfigMemorySizeSerializer")
        }
    }

    private fun Config.decodeMemorySize(path: String): ConfigMemorySize = try {
        getMemorySize(path)
    } catch (e: ConfigException) {
        throw SerializationException("Value at $path cannot be read as ConfigMemorySize because it is not a valid HOCON Size value", e)
    }
}