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

name.remal.gradle_plugins.dsl.Version.kt Maven / Gradle / Ivy

package name.remal.gradle_plugins.dsl

import name.remal.arrayEquals
import name.remal.default
import name.remal.escapeRegex
import java.io.Serializable
import java.time.Instant.ofEpochMilli
import java.time.LocalDateTime
import java.time.LocalDateTime.ofInstant
import java.time.ZoneId
import java.time.temporal.ChronoField.*
import java.util.*

class Version @JvmOverloads constructor(
    vararg numbers: Int,
    suffix: String? = null
) : Comparable, Serializable {

    @JvmOverloads constructor(major: Int, suffix: String? = null) : this(*intArrayOf(major), suffix = suffix)
    @JvmOverloads constructor(major: Int, minor: Int, suffix: String? = null) : this(*intArrayOf(major, minor), suffix = suffix)
    @JvmOverloads constructor(major: Int, minor: Int, patch: Int, suffix: String? = null) : this(*intArrayOf(major, minor, patch), suffix = suffix)
    @JvmOverloads constructor(major: Int, minor: Int, patch: Int, build: Int, suffix: String? = null) : this(*intArrayOf(major, minor, patch, build), suffix = suffix)

    private val numbers: IntArray

    val suffix: String

    init {
        if (numbers.isEmpty()) throw IllegalArgumentException("numbers is empty")
        numbers.forEachIndexed { index, number ->
            if (number < 0) {
                throw IllegalArgumentException(buildString {
                    when (index) {
                        0 -> append("major")
                        1 -> append("minor")
                        2 -> append("patch")
                        3 -> append("build")
                        else -> append("numbers[").append(index).append("]")
                    }
                    append(" < 0")
                })
            }
        }

        this.numbers = numbers.copyOf()
        this.suffix = suffix.default()
    }

    override fun toString() = buildString {
        numbers.forEachIndexed { index, number ->
            if (1 <= index) append(NUMBERS_DELIM)
            append(number)
        }
        if (suffix.isNotEmpty()) append(SUFFIX_DELIM).append(suffix)
    }

    override fun compareTo(other: Version): Int {
        val commonLength = Math.min(numbers.size, other.numbers.size)
        (0 until commonLength).forEach { numbers[it].compareTo(other.numbers[it]).let { if (0 != it) return it } }
        if (numbers.size < commonLength) return -1
        if (other.numbers.size < commonLength) return 1
        return suffix.compareTo(other.suffix)
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (null == other || other !is Version) return false
        return numbers.arrayEquals(other.numbers) && suffix == other.suffix
    }

    override fun hashCode(): Int {
        var result = 1
        numbers.forEach { result = 31 * result + Integer.hashCode(it) }
        result = 31 * result + suffix.hashCode()
        return result
    }

    val numbersCount get() = numbers.size
    fun getNumberOrNull(index: Int) = numbers.getOrNull(index)
    fun getNumberOr0(index: Int) = if (index < numbers.size) numbers[index] else 0

    fun withNumber(index: Int, number: Int): Version {
        if (index < numbers.size && numbers[index] == number) return this
        return Version(
            *numbers.copyOf(Math.max(numbers.size, index + 1)).apply { this[index] = number },
            suffix = suffix
        )
    }

    fun incrementNumber(index: Int, increment: Int = 1): Version {
        if (index < numbers.size && 0 == increment) return this
        return withNumber(index, getNumberOr0(index) + increment)
    }

    val major get() = numbers[0]
    fun withMajor(major: Int) = withNumber(0, major)
    fun incrementMajor(increment: Int = 1) = incrementNumber(0, increment)

    val minor get() = getNumberOrNull(1)
    val hasMinor get() = 2 <= numbers.size
    fun withMinor(minor: Int) = withNumber(1, minor)
    fun incrementMinor(increment: Int = 1) = incrementNumber(1, increment)

    val patch get() = getNumberOrNull(2)
    val hasPatch get() = 3 <= numbers.size
    fun withPatch(patch: Int) = withNumber(2, patch)
    fun incrementPatch(increment: Int = 1) = incrementNumber(2, increment)

    val build get() = getNumberOrNull(3)
    val hasBuild get() = 4 <= numbers.size
    fun withBuild(build: Int) = withNumber(3, build)
    fun incrementBuild(increment: Int = 1) = incrementNumber(3, increment)

    val hasSuffix get() = suffix.isNotEmpty()
    fun withSuffix(suffix: String?): Version {
        if (suffix.default() == this.suffix) return this
        return Version(*numbers, suffix = suffix)
    }

    fun appendSuffix(suffixPart: String, delimiter: String = "") = withSuffix(buildString {
        append(suffix)
        if (suffix.isNotEmpty()) append(delimiter)
        append(suffixPart)
    })

    fun prependSuffix(suffixPart: String, delimiter: String = "") = withSuffix(buildString {
        append(suffixPart)
        if (suffix.isNotEmpty()) append(delimiter)
        append(suffix)
    })

    companion object {

        private const val NUMBERS_DELIM = "."
        private const val SUFFIX_DELIM = "-"

        private val PATTERN = Regex("\\d+(${escapeRegex(NUMBERS_DELIM)}\\d+)*(${escapeRegex(SUFFIX_DELIM)}.*)?")

        @JvmStatic
        fun parse(string: String): Version {
            if (!string.matches(PATTERN)) throw ParseVersionException("'$string' doesn't match '$PATTERN'")
            val stringNumbers = string.substringBefore(SUFFIX_DELIM).split(NUMBERS_DELIM)
            val numbers = IntArray(stringNumbers.size)
            stringNumbers.forEachIndexed { index, stringNumber -> numbers[index] = stringNumber.toInt() }
            val suffix = string.substringAfter(SUFFIX_DELIM, "")
            return Version(
                *numbers,
                suffix = suffix
            )
        }

        @JvmStatic
        fun parseOrNull(string: String): Version? {
            if (!string.matches(PATTERN)) return null
            try {
                return parse(string)
            } catch (ignored: Exception) {
                return null
            }
        }

        @JvmStatic
        fun ofLocalDateTime(dateTime: LocalDateTime): Version {
            return dateTime.atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneId.of("UTC")).let {
                Version(
                    it.get(YEAR),
                    it.get(MONTH_OF_YEAR),
                    it.get(DAY_OF_MONTH),
                    "%02d%02d%02d".format(it.get(HOUR_OF_DAY), it.get(MINUTE_OF_HOUR), it.get(SECOND_OF_MINUTE))
                )
            }
        }

        @JvmStatic
        fun ofTimestamp(timestamp: Long) = ofLocalDateTime(ofEpochMilli(timestamp).atZone(ZoneId.systemDefault()).toLocalDateTime())

        @JvmStatic
        fun ofDate(date: Date) = ofLocalDateTime(ofInstant(date.toInstant(), ZoneId.systemDefault()))

    }

}

class ParseVersionException : RuntimeException {
    constructor() : super()
    constructor(message: String?) : super(message)
    constructor(message: String?, cause: Throwable?) : super(message, cause)
    constructor(cause: Throwable?) : super(cause)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy