All Downloads are FREE. Search and download functionalities are using the official Maven repository.

loggersoft.kotlin.utils.IntegerFormatter.kt Maven / Gradle / Ivy

/*
 * Copyright (C) 2018 Alexander Kornilov ([email protected])
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package loggersoft.kotlin.utils

import java.text.ParseException

/**
 * Backend for formatting integers by string pattern.
 *
 * ### Format description: `+0(number)b|B|x|X`
 * - `+` - forces to proceed the result with a plus or minus sign (+ or -) even for positive numbers.
 *         By default, only negative numbers are preceded with a minus sign.
 * - `0` - left-pads the number with zeroes (0) instead of spaces when padding is specified.
 * - `number` - minimum number of characters to be printed. If the value to be printed is shorter than this number, the result is padded with blank spaces.
 *              The value is not truncated even if the result is larger.
 * - `b|B` - binary representation.
 * - `x` - hexadecimal representation.
 * - `X` - hexadecimal representation (uppercase).
 *
 * ### Examples:
 * - format="+05", number=3 => "+0003"
 * - format="+5", number=3 => "   +3"
 * - format="x", number=255 => "ff"
 * - format="04X", number=255 => "00FF"
 *
 * @param format pattern for formatting (see details above).
 * @throws ParseException
 *
 * @author Alexander Kornilov ([email protected]).
 */
class IntegerFormatter(format: String) {

    /**
     * Indicates that sign should be displayed even for positive numbers.
     */
    val sign: Boolean

    /**
     * Minimum number of characters to be printed.
     */
    val pad: Int

    /**
     * Indicates that pad should be zeros.
     */
    val padZero: Boolean

    /**
     * The radix of the number representation.
     * Default value is 10.
     */
    val radix: Int

    /**
     * Indicates that digits of the number which represented as a letter (e.g. in hex) should be in uppercase.
     */
    val capital: Boolean

    init {
        val result = Regex("""^\s*(\+?)\s*(\d*)\s*([xXbB]?)\s*$""").matchEntire(format) ?: throw ParseException(format, -1)
        sign = !result.groups[1]?.value.isNullOrEmpty()
        val padGroup = result.groups[2]?.value
        if (padGroup != null &&  padGroup.isNotEmpty()) { pad = padGroup.toInt(); padZero = padGroup[0] == '0' } else { pad = 0; padZero = false }
        val radixGroup = result.groups[3]?.value
        if (radixGroup != null && radixGroup.isNotEmpty()) {
            when(radixGroup[0]) {
                'b' -> { radix = 2; capital = false }
                'B' -> { radix = 2; capital = true }
                'x' -> { radix = 16; capital = false }
                'X' -> { radix = 16; capital = true }
                else -> { radix = 10; capital = false }
            }
        } else { radix = 10; capital = false }
    }

    /**
     * Formats digits according settings: [sign], [pad], [padZero], [radix] and [capital].
     * @param negative indicates that the source number is negative.
     * @param initValue initial value to pass into [getDigit].
     * @param [getDigit] returns pair with value to process and next digit or `null` if the end of number reached.
     *
     * @return string representation of number according to format.
     */
    inline fun  format(negative: Boolean, initValue: T, getDigit: (radix: Int, value: T) -> Pair?): String {
        val digits = mutableListOf()
        var value = initValue
        var isFirst = true
        while(true) {
            val (nextValue, digit) = getDigit(radix, value) ?: break
            require(digit in 0 until radix)
            value = nextValue
            if (digit == 0) { if (isFirst) continue } else isFirst = false
            digits.add(0, digit)
        }
        val result = StringBuilder()
        val isZero = digits.isEmpty()
        val signPrefix = if (isZero) String.Empty else if (negative) "-" else if (sign) "+" else String.Empty
        val digitsCount = signPrefix.length + if (isZero) 1 else digits.size
        if (padZero) result.append(signPrefix)
        if (digitsCount < pad) (pad - digitsCount).repeat { result.append(if (padZero) '0' else ' ') }
        if (!padZero) result.append(signPrefix)
        if (!isZero) for (digit in digits) {
            result.append((digit + if (digit in 0..9) 48 else if (capital) 55 else 87).toChar())
        } else result.append('0')
        return result.toString()
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy