commonMain.kotlin.text.HexFormat.kt Maven / Gradle / Ivy
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package kotlin.text
import kotlin.internal.InlineOnly
/**
* Represents hexadecimal format options.
*
* To create a new [HexFormat] use `HexFormat` function.
*/
@ExperimentalStdlibApi
@SinceKotlin("1.9")
public class HexFormat internal constructor(
/**
* Specifies whether upper case hexadecimal digits `0-9`, `A-F` should be used for formatting.
* If `false`, lower case hexadecimal digits `0-9`, `a-f` will be used.
*
* Affects both `ByteArray` and numeric value formatting.
*/
val upperCase: Boolean,
/**
* Specifies hexadecimal format used for formatting and parsing `ByteArray`.
*/
val bytes: BytesHexFormat,
/**
* Specifies hexadecimal format used for formatting and parsing a numeric value.
*/
val number: NumberHexFormat
) {
override fun toString(): String = buildString {
append("HexFormat(").appendLine()
append(" upperCase = ").append(upperCase).appendLine(",")
append(" bytes = BytesHexFormat(").appendLine()
bytes.appendOptionsTo(this, indent = " ").appendLine()
append(" ),").appendLine()
append(" number = NumberHexFormat(").appendLine()
number.appendOptionsTo(this, indent = " ").appendLine()
append(" )").appendLine()
append(")")
}
/**
* Represents hexadecimal format options for formatting and parsing `ByteArray`.
*
* When formatting one can assume that bytes are firstly separated using LF character (`'\n'`) into lines
* with [bytesPerLine] bytes in each line. The last line may have fewer bytes.
* Then each line is separated into groups using [groupSeparator] with [bytesPerGroup] bytes in each group,
* except the last group in the line, which may have fewer bytes.
* All bytes in a group are separated using [byteSeparator].
* Each byte is converted to its two-digit hexadecimal representation,
* immediately preceded by [bytePrefix] and immediately succeeded by [byteSuffix].
*
* When parsing the input string is required to be in the format described above.
* However, any of the char sequences CRLF, LF and CR is considered a valid line separator,
* and parsing is performed in case-insensitive manner.
*
* See [BytesHexFormat.Builder] to find out how the options are configured,
* and what is the default value of each option.
*/
public class BytesHexFormat internal constructor(
/** The maximum number of bytes per line. */
val bytesPerLine: Int,
/** The maximum number of bytes per group. */
val bytesPerGroup: Int,
/** The string used to separate adjacent groups in a line. */
val groupSeparator: String,
/** The string used to separate adjacent bytes in a group. */
val byteSeparator: String,
/** The string that immediately precedes two-digit hexadecimal representation of each byte. */
val bytePrefix: String,
/** The string that immediately succeeds two-digit hexadecimal representation of each byte. */
val byteSuffix: String
) {
override fun toString(): String = buildString {
append("BytesHexFormat(").appendLine()
appendOptionsTo(this, indent = " ").appendLine()
append(")")
}
internal fun appendOptionsTo(sb: StringBuilder, indent: String): StringBuilder {
sb.append(indent).append("bytesPerLine = ").append(bytesPerLine).appendLine(",")
sb.append(indent).append("bytesPerGroup = ").append(bytesPerGroup).appendLine(",")
sb.append(indent).append("groupSeparator = \"").append(groupSeparator).appendLine("\",")
sb.append(indent).append("byteSeparator = \"").append(byteSeparator).appendLine("\",")
sb.append(indent).append("bytePrefix = \"").append(bytePrefix).appendLine("\",")
sb.append(indent).append("byteSuffix = \"").append(byteSuffix).append("\"")
return sb
}
/**
* A context for building a [BytesHexFormat]. Provides API for configuring format options.
*/
class Builder internal constructor() {
/**
* Defines [BytesHexFormat.bytesPerLine] of the format being built, [Int.MAX_VALUE] by default.
*
* The value must be positive.
*
* @throws IllegalArgumentException if a non-positive value is assigned to this property.
*/
var bytesPerLine: Int = Default.bytesPerLine
set(value) {
if (value <= 0)
throw IllegalArgumentException("Non-positive values are prohibited for bytesPerLine, but was $value")
field = value
}
/**
* Defines [BytesHexFormat.bytesPerGroup] of the format being built, [Int.MAX_VALUE] by default.
*
* The value must be positive.
*
* @throws IllegalArgumentException if a non-positive value is assigned to this property.
*/
var bytesPerGroup: Int = Default.bytesPerGroup
set(value) {
if (value <= 0)
throw IllegalArgumentException("Non-positive values are prohibited for bytesPerGroup, but was $value")
field = value
}
/** Defines [BytesHexFormat.groupSeparator] of the format being built, two space characters (`" "`) by default. */
var groupSeparator: String = Default.groupSeparator
/**
* Defines [BytesHexFormat.byteSeparator] of the format being built, empty string by default.
*
* The string must not contain LF and CR characters.
*
* @throws IllegalArgumentException if a string containing LF or CR character is assigned to this property.
*/
var byteSeparator: String = Default.byteSeparator
set(value) {
if (value.contains('\n') || value.contains('\r'))
throw IllegalArgumentException("LF and CR characters are prohibited in byteSeparator, but was $value")
field = value
}
/**
* Defines [BytesHexFormat.bytePrefix] of the format being built, empty string by default.
*
* The string must not contain LF and CR characters.
*
* @throws IllegalArgumentException if a string containing LF or CR character is assigned to this property.
*/
var bytePrefix: String = Default.bytePrefix
set(value) {
if (value.contains('\n') || value.contains('\r'))
throw IllegalArgumentException("LF and CR characters are prohibited in bytePrefix, but was $value")
field = value
}
/**
* Defines [BytesHexFormat.byteSuffix] of the format being built, empty string by default.
*
* The string must not contain LF and CR characters.
*
* @throws IllegalArgumentException if a string containing LF or CR character is assigned to this property.
*/
var byteSuffix: String = Default.byteSuffix
set(value) {
if (value.contains('\n') || value.contains('\r'))
throw IllegalArgumentException("LF and CR characters are prohibited in byteSuffix, but was $value")
field = value
}
internal fun build(): BytesHexFormat {
return BytesHexFormat(bytesPerLine, bytesPerGroup, groupSeparator, byteSeparator, bytePrefix, byteSuffix)
}
}
internal companion object {
internal val Default = BytesHexFormat(
bytesPerLine = Int.MAX_VALUE,
bytesPerGroup = Int.MAX_VALUE,
groupSeparator = " ",
byteSeparator = "",
bytePrefix = "",
byteSuffix = ""
)
}
}
/**
* Represents hexadecimal format options for formatting and parsing a numeric value.
*
* The formatting result consist of [prefix] string, hexadecimal representation of the value being formatted, and [suffix] string.
* Hexadecimal representation of a value is calculated by mapping each four-bit chunk
* of its binary representation to the corresponding hexadecimal digit, starting with the most significant bits.
* [upperCase] determines whether upper case `0-9`, `A-F` or lower case `0-9`, `a-f` hexadecimal digits are used.
* If [removeLeadingZeros] it `true`, leading zeros in the hexadecimal representation are removed.
*
* For example, the binary representation of the `Byte` value `58` is the 8-bit long `00111010`,
* which converts to a hexadecimal representation of `3a` or `3A` depending on [upperCase].
* Whereas, the binary representation of the `Int` value `58` is the 32-bit long `00000000000000000000000000111010`,
* which converts to a hexadecimal representation of `0000003a` or `0000003A` depending on [upperCase].
* If [removeLeadingZeros] it `true`, leading zeros in `0000003a` are removed, resulting `3a`.
*
* To convert a value to hexadecimal string of a particular length,
* first convert the value to a type with the corresponding bit size.
* For example, to convert an `Int` value to 4-digit hexadecimal string,
* convert the value `toShort()` before hexadecimal formatting.
* To convert it to hexadecimal string of at most 4 digits
* without leading zeros, set [removeLeadingZeros] to `true` in addition.
*
* Parsing requires [prefix] and [suffix] to be present in the input string,
* and the amount of hexadecimal digits to be at least one and at most the value bit size divided by four.
* Parsing is performed in case-insensitive manner, and [removeLeadingZeros] is ignored as well.
*
* See [NumberHexFormat.Builder] to find out how the options are configured,
* and what is the default value of each option.
*/
public class NumberHexFormat internal constructor(
/** The string that immediately precedes hexadecimal representation of a numeric value. */
val prefix: String,
/** The string that immediately succeeds hexadecimal representation of a numeric value. */
val suffix: String,
/** Specifies whether to remove leading zeros in the hexadecimal representation of a numeric value. */
val removeLeadingZeros: Boolean
) {
override fun toString(): String = buildString {
append("NumberHexFormat(").appendLine()
appendOptionsTo(this, indent = " ").appendLine()
append(")")
}
internal fun appendOptionsTo(sb: StringBuilder, indent: String): StringBuilder {
sb.append(indent).append("prefix = \"").append(prefix).appendLine("\",")
sb.append(indent).append("suffix = \"").append(suffix).appendLine("\",")
sb.append(indent).append("removeLeadingZeros = ").append(removeLeadingZeros)
return sb
}
/**
* A context for building a [NumberHexFormat]. Provides API for configuring format options.
*/
class Builder internal constructor() {
/**
* Defines [NumberHexFormat.prefix] of the format being built, empty string by default.
*
* The string must not contain LF and CR characters.
*
* @throws IllegalArgumentException if a string containing LF or CR character is assigned to this property.
*/
var prefix: String = Default.prefix
set(value) {
if (value.contains('\n') || value.contains('\r'))
throw IllegalArgumentException("LF and CR characters are prohibited in prefix, but was $value")
field = value
}
/**
* Defines [NumberHexFormat.suffix] of the format being built, empty string by default.
*
* The string must not contain LF and CR characters.
*
* @throws IllegalArgumentException if a string containing LF or CR character is assigned to this property.
*/
var suffix: String = Default.suffix
set(value) {
if (value.contains('\n') || value.contains('\r'))
throw IllegalArgumentException("LF and CR characters are prohibited in suffix, but was $value")
field = value
}
/** Defines [NumberHexFormat.removeLeadingZeros] of the format being built, empty string by default. */
var removeLeadingZeros: Boolean = Default.removeLeadingZeros
internal fun build(): NumberHexFormat {
return NumberHexFormat(prefix, suffix, removeLeadingZeros)
}
}
internal companion object {
internal val Default = NumberHexFormat(
prefix = "",
suffix = "",
removeLeadingZeros = false
)
}
}
/**
* A context for building a [HexFormat]. Provides API for configuring format options.
*/
public class Builder @PublishedApi internal constructor() {
/** Defines [HexFormat.upperCase] of the format being built, `false` by default. */
var upperCase: Boolean = Default.upperCase
/**
* Defines [HexFormat.bytes] of the format being built.
*
* See [BytesHexFormat.Builder] for default values of the options.
*/
val bytes: BytesHexFormat.Builder
get() {
if (_bytes == null) {
_bytes = BytesHexFormat.Builder()
}
return _bytes!!
}
private var _bytes: BytesHexFormat.Builder? = null
/**
* Defines [HexFormat.number] of the format being built.
*
* See [NumberHexFormat.Builder] for default values of the options.
*/
val number: NumberHexFormat.Builder
get() {
if (_number == null) {
_number = NumberHexFormat.Builder()
}
return _number!!
}
private var _number: NumberHexFormat.Builder? = null
/**
* Provides a scope for configuring the [HexFormat.bytes] format options.
*
* See [BytesHexFormat.Builder] for default values of the options.
*/
@InlineOnly
inline fun bytes(builderAction: BytesHexFormat.Builder.() -> Unit) {
bytes.builderAction()
}
/**
* Provides a scope for configuring the [HexFormat.number] format options.
*
* See [NumberHexFormat.Builder] for default values of the options.
*/
@InlineOnly
inline fun number(builderAction: NumberHexFormat.Builder.() -> Unit) {
number.builderAction()
}
@PublishedApi
internal fun build(): HexFormat {
return HexFormat(
upperCase,
_bytes?.build() ?: BytesHexFormat.Default,
_number?.build() ?: NumberHexFormat.Default
)
}
}
companion object {
/**
* The default hexadecimal format options.
*
* Uses lower case hexadecimal digits `0-9`, `a-f` when formatting
* both `ByteArray` and numeric values. That is [upperCase] is `false`.
*
* No line separator, group separator, byte separator, byte prefix or byte suffix is used
* when formatting or parsing `ByteArray`. That is:
* * [BytesHexFormat.bytesPerLine] is `Int.MAX_VALUE`.
* * [BytesHexFormat.bytesPerGroup] is `Int.MAX_VALUE`.
* * [BytesHexFormat.byteSeparator], [BytesHexFormat.bytePrefix] and [BytesHexFormat.byteSuffix] are empty strings.
*
* No prefix or suffix is used, and no leading zeros in hexadecimal representation are removed
* when formatting or parsing a numeric value. That is:
* * [NumberHexFormat.prefix] and [NumberHexFormat.suffix] are empty strings.
* * [NumberHexFormat.removeLeadingZeros] is `false`.
*/
public val Default: HexFormat = HexFormat(
upperCase = false,
bytes = BytesHexFormat.Default,
number = NumberHexFormat.Default,
)
/**
* Uses upper case hexadecimal digits `0-9`, `A-F` when formatting
* both `ByteArray` and numeric values. That is [upperCase] is `true`.
*
* The same as [Default] format in other aspects.
*/
public val UpperCase: HexFormat = HexFormat(
upperCase = true,
bytes = BytesHexFormat.Default,
number = NumberHexFormat.Default,
)
}
}
/**
* Builds a new [HexFormat] by configuring its format options using the specified [builderAction],
* and returns the resulting format.
*
* The builder passed as a receiver to the [builderAction] is valid only inside that function.
* Using it outside of the function produces an unspecified behavior.
*/
@ExperimentalStdlibApi
@SinceKotlin("1.9")
@InlineOnly
public inline fun HexFormat(builderAction: HexFormat.Builder.() -> Unit): HexFormat {
return HexFormat.Builder().apply(builderAction).build()
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy