Skip to content

Commit 2205885

Browse files
committed
Restructure SemanticVersion and make it more fool-proof
1 parent 144063c commit 2205885

File tree

3 files changed

+51
-19
lines changed

3 files changed

+51
-19
lines changed

src/main/kotlin/com/demonwav/mcdev/platform/forge/ForgeTemplate.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import com.intellij.openapi.vfs.VirtualFile
1919
import java.util.Properties
2020

2121
object ForgeTemplate {
22-
private val MC_1_12 = SemanticVersion("1.12")
22+
private val MC_1_12 = SemanticVersion.parse("1.12")
2323

2424
fun applyBuildGradleTemplate(project: Project,
2525
file: VirtualFile,
@@ -35,7 +35,7 @@ object ForgeTemplate {
3535
properties.setProperty("SPONGE_FORGE", "true")
3636
}
3737
// Fixes builds for MC1.12+, requires FG 2.3
38-
val mcVersion = SemanticVersion(configuration.mcVersion)
38+
val mcVersion = SemanticVersion.parse(configuration.mcVersion)
3939
if (mcVersion >= MC_1_12) {
4040
properties.setProperty("FORGEGRADLE_VERSION", "2.3")
4141
} else {

src/main/kotlin/com/demonwav/mcdev/platform/forge/version/ForgeVersion.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,16 @@ class ForgeVersion private constructor(private val map: Map<*, *>) {
2424
}
2525

2626
fun getRecommended(versions: List<String>): String {
27-
var recommended = SemanticVersion("1.7")
27+
var recommended = SemanticVersion.parse("1.7")
2828
for (version in versions) {
2929
getPromo(version) ?: continue
30-
val semantic = SemanticVersion(version)
30+
val semantic = SemanticVersion.parse(version)
3131
if (recommended < semantic) {
3232
recommended = semantic
3333
}
3434
}
3535

36-
return recommended.value
36+
return recommended.toString()
3737
}
3838

3939
fun getPromo(version: String): Double? {

src/main/kotlin/com/demonwav/mcdev/util/SemanticVersion.kt

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@ package com.demonwav.mcdev.util
1313
import com.demonwav.mcdev.util.SemanticVersion.Companion.VersionPart.PreReleasePart
1414
import com.demonwav.mcdev.util.SemanticVersion.Companion.VersionPart.ReleasePart
1515

16-
class SemanticVersion(val value: String) : Comparable<SemanticVersion> {
17-
val parts = value.split(".").map {
18-
if (it.contains("_pre")) {
19-
val subParts = it.split("_pre")
20-
PreReleasePart(subParts[0].toInt(), subParts[1].toInt())
21-
} else {
22-
ReleasePart(it.toInt())
23-
}
24-
}
25-
16+
/**
17+
* Represents a comparable and generalised "semantic version".
18+
* Each constituent part (delimited by periods in a version string) contributes
19+
* to the version ranking with decreasing priority from left to right.
20+
*/
21+
class SemanticVersion(val parts: List<VersionPart>) : Comparable<SemanticVersion> {
2622
override fun compareTo(other: SemanticVersion): Int {
27-
val result = parts.zip(other.parts).fold(0, { acc, (a, b) -> if (acc == -1) acc else a.compareTo(b) })
23+
// Zipping limits the compared parts to the shorter version, then we perform a component-wise comparison
24+
// Short-circuits if any component of this version is smaller/older
25+
val result = parts.zip(other.parts).fold(0) { acc, (a, b) -> if (acc == -1) acc else a.compareTo(b) }
26+
// When all the parts are equal, the longer version wins
27+
// Generally speaking, 1.0 is considered older than 1.0.1 (see MC 1.12 vs 1.12.1)
2828
if (parts.size != other.parts.size && result == 0) {
2929
return parts.size - other.parts.size
3030
}
@@ -37,18 +37,48 @@ class SemanticVersion(val value: String) : Comparable<SemanticVersion> {
3737
else -> false
3838
}
3939

40-
override fun hashCode(): Int {
41-
return parts.hashCode()
42-
}
40+
override fun hashCode() = parts.hashCode()
41+
42+
override fun toString() = parts.map { it.toString() }.joinToString(".")
4343

4444
companion object {
45+
/**
46+
* Parses a version string into a comparable representation.
47+
* @throws IllegalArgumentException if any part of the version string cannot be parsed as integer or split into pre-release parts.
48+
*/
49+
fun parse(value: String): SemanticVersion {
50+
fun parseInt(part: String): Int =
51+
if (part.all { it.isDigit() })
52+
part.toInt()
53+
else
54+
throw IllegalArgumentException("Failed to parse version part as integer: $part")
55+
56+
val parts = value.split('.').map {
57+
if (it.contains("_pre")) {
58+
// There have been cases of Forge builds for MC pre-releases (1.7.10_pre4)
59+
// We're consuming the 10_pre4 and extracting 10 and 4 from it
60+
val subParts = it.split("_pre")
61+
if (subParts.size == 2) {
62+
PreReleasePart(parseInt(subParts[0]), parseInt(subParts[1]))
63+
} else {
64+
throw IllegalArgumentException("Failed to split pre-release version part into two numbers: $it")
65+
}
66+
} else {
67+
ReleasePart(parseInt(it))
68+
}
69+
}
70+
return SemanticVersion(parts)
71+
}
72+
4573
sealed class VersionPart : Comparable<VersionPart> {
4674
data class ReleasePart(val version: Int) : VersionPart() {
4775
override fun compareTo(other: VersionPart) =
4876
when (other) {
4977
is PreReleasePart -> if (version != other.version) version - other.version else 1
5078
is ReleasePart -> version - other.version
5179
}
80+
81+
override fun toString() = version.toString()
5282
}
5383

5484
data class PreReleasePart(val version: Int, val pre: Int) : VersionPart() {
@@ -57,6 +87,8 @@ class SemanticVersion(val value: String) : Comparable<SemanticVersion> {
5787
is PreReleasePart -> if (version != other.version) version - other.version else pre - other.pre
5888
is ReleasePart -> if (version != other.version) version - other.version else -1
5989
}
90+
91+
override fun toString() = "${version}_pre$pre"
6092
}
6193
}
6294
}

0 commit comments

Comments
 (0)