<lambda>null1 package kotlinx.serialization.hocon.serializers
2 
3 import com.typesafe.config.*
4 import java.math.BigInteger
5 import kotlinx.serialization.*
6 import kotlinx.serialization.descriptors.*
7 import kotlinx.serialization.encoding.*
8 import kotlinx.serialization.hocon.*
9 
10 /**
11  * Serializer for [ConfigMemorySize].
12  * All possible Hocon size formats [https://github.com/lightbend/config/blob/main/HOCON.md#size-in-bytes-format] are accepted for decoding.
13  * During encoding, the serializer emits values using powers of two: byte, KiB, MiB, GiB, TiB, PiB, EiB, ZiB, YiB.
14  * Encoding uses the largest possible integer value.
15  * Example:
16  *  1024 byte -> 1 KiB;
17  *  1024 KiB -> 1 MiB;
18  *  1025 KiB -> 1025 KiB.
19  * Usage example:
20  * ```
21  * @Serializable
22  * data class ConfigMemory(
23  *      @Serializable(ConfigMemorySizeSerializer::class)
24  *      val size: ConfigMemorySize
25  * )
26  * val config = ConfigFactory.parseString("size = 1 MiB")
27  * val configMemory = Hocon.decodeFromConfig(ConfigMemory.serializer(), config)
28  * val newConfig = Hocon.encodeToConfig(ConfigMemory.serializer(), configMemory)
29  * ```
30  */
31 @ExperimentalSerializationApi
32 object ConfigMemorySizeSerializer : KSerializer<ConfigMemorySize> {
33 
34     // For powers of two.
35     private val memoryUnitFormats = listOf("byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB")
36 
37     override val descriptor: SerialDescriptor =
38         PrimitiveSerialDescriptor("hocon.com.typesafe.config.ConfigMemorySize", PrimitiveKind.STRING)
39 
40     override fun deserialize(decoder: Decoder): ConfigMemorySize =
41         if (decoder is HoconDecoder) decoder.decodeConfigValue { conf, path -> conf.decodeMemorySize(path) }
42         else throwUnsupportedFormatException("ConfigMemorySizeSerializer")
43 
44     override fun serialize(encoder: Encoder, value: ConfigMemorySize) {
45         if (encoder is HoconEncoder) {
46             // We determine that it is divisible by 1024 (2^10).
47             // And if it is divisible, then the number itself is shifted to the right by 10.
48             // And so on until we find one that is no longer divisible by 1024.
49             // ((n & ((1 << m) - 1)) == 0)
50             val andVal = BigInteger.valueOf(1023) // ((2^10) - 1) = 0x3ff = 1023
51             var bytes = value.toBytesBigInteger()
52             var unitIndex = 0
53             while (bytes.and(andVal) == BigInteger.ZERO) { // n & 0x3ff == 0
54                 if (unitIndex < memoryUnitFormats.lastIndex) {
55                     bytes = bytes.shiftRight(10)
56                     unitIndex++
57                 } else break
58             }
59             encoder.encodeString("$bytes ${memoryUnitFormats[unitIndex]}")
60         } else {
61             throwUnsupportedFormatException("ConfigMemorySizeSerializer")
62         }
63     }
64 
65     private fun Config.decodeMemorySize(path: String): ConfigMemorySize = try {
66         getMemorySize(path)
67     } catch (e: ConfigException) {
68         throw SerializationException("Value at $path cannot be read as ConfigMemorySize because it is not a valid HOCON Size value", e)
69     }
70 }
71