Skip to content

Commit

Permalink
Add BukkitVersions to simplify parsing MC/CB versions
Browse files Browse the repository at this point in the history
Bit of a mess, but I think it's about the best I can do.
  • Loading branch information
Jikoo committed Nov 4, 2023
1 parent ee17b74 commit 624e49e
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.github.jikoo.planarwrappers.util.version;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.VisibleForTesting;

public final class BukkitVersions {

/**
* The Minecraft version.
*/
public static final Version MINECRAFT;
/**
* The {@link Version} of Craftbukkit's package. Do not confuse with {@link #MINECRAFT}!
*
* <p>The major and minor version correspond to Minecraft's major and minor version. The patch
* version is an internal version that is intended to be bumped whenever the Minecraft mappings
* version ({@code org.bukkit.craftbukkit.util.CraftMagicNumbers#getMappingsVersion}) changes.
*
* <p>Note that if you are writing a plugin using NMS, you should prefer checking against the
* mappings version. Spigot has been very unreliable about bumping the minor version, claiming
* that the mappings version is the ultimate way to check if your NMS usage is guaranteed to be
* compatible. This is true, but only because they're unreliable about bumping it. If they
* actually kept up with bumping it (which could have been scripted) it would be just as reliable
* and much more human-friendly.
* @deprecated Prefer {@code org.bukkit.craftbukkit.util.CraftMagicNumbers#getMappingsVersion}
*/
@Deprecated
public static final Version CRAFTBUKKIT_PACKAGE;

static {
Server server = Bukkit.getServer();
// Note: we use Bukkit version and not server version because server version includes prefixes
// (such as implementation name and commit hashes of build version) that are not presented in
// valid SemVer format.
// Bukkit version should be a (mostly) SemVer-compliant version including Minecraft's version.
String bukkitVersion = server.getBukkitVersion();
MINECRAFT = parseMinecraftVersion(bukkitVersion);

String packageString = server.getClass().getPackage().toString();
CRAFTBUKKIT_PACKAGE = parseCraftbukkitVersion(packageString);
}

@VisibleForTesting
@Contract("_ -> new")
static @NotNull Version parseMinecraftVersion(@NotNull String bukkitVersion) {
Pattern semVerRelease = Pattern.compile("^(\\d+)\\.(\\d+)(\\.(\\d+))?-.*$");
Matcher matcher = semVerRelease.matcher(bukkitVersion);

if (matcher.find()) {
String patch = matcher.group(3);
return new IntVersion(
Integer.parseInt(matcher.group(1)),
Integer.parseInt(matcher.group(2)),
patch == null || patch.isEmpty() ? 0 : Integer.parseInt(matcher.group(4))
);
}

// If Bukkit version has been modified (oh boy) fall through to using whatever Version selects
// with extra SemVer details stripped. Spigot hasn't ever cut a Bukkit release, so
// the pre-release data only will serve to cause comparison confusion.
return Version.of(stripExtraData(bukkitVersion));
}

@VisibleForTesting
static @NotNull Version parseCraftbukkitVersion(@NotNull String craftbukkitPackage) {
Pattern packageVer = Pattern.compile("^org\\.bukkit\\.craftbukkit\\.v(\\d+)_(\\d+)_R?(\\d+)");
Matcher matcher = packageVer.matcher(craftbukkitPackage);

if (matcher.find()) {
return new IntVersion(
Integer.parseInt(matcher.group(1)),
Integer.parseInt(matcher.group(2)),
Integer.parseInt(matcher.group(3))
);
}

// Ancient/unknown version; package is not versioned. Best bet is to use the Minecraft version.
return MINECRAFT;
}

private static @NotNull String stripExtraData(@NotNull String version) {
int preReleaseStart = version.indexOf('-');
int metadataStart = version.indexOf('+');
if (preReleaseStart > -1) {
if (metadataStart > -1) {
return version.substring(0, Math.min(preReleaseStart, metadataStart));
} else {
return version.substring(0, preReleaseStart);
}
} else {
if (metadataStart > -1) {
return version.substring(0, metadataStart);
}
}
return version;
}

private BukkitVersions() {
throw new IllegalStateException("Cannot instantiate static utility classes!");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import static org.hamcrest.Matchers.arrayWithSize;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.doReturn;

import com.github.jikoo.planarwrappers.mock.ServerMocks;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
Expand All @@ -17,7 +19,10 @@
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.params.ParameterizedTest;
Expand All @@ -28,6 +33,13 @@ class UninstantiableUtilTest {

private static Collection<? extends Class<?>> utilClasses = null;

@BeforeAll
void setUp() {
Server server = ServerMocks.newServer();
doReturn("1.0.0-FAKE").when(server).getBukkitVersion();
Bukkit.setServer(server);
}

@ParameterizedTest
@MethodSource("getUtilClasses")
void isFinal(@NotNull Class<?> clazz) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.github.jikoo.planarwrappers.util.version;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.doReturn;

import com.github.jikoo.planarwrappers.mock.ServerMocks;
import java.util.Collection;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;

@TestInstance(Lifecycle.PER_CLASS)
class BukkitVersionsTest {

@BeforeAll
void beforeAll() {
Server server = ServerMocks.newServer();
doReturn("1.19.4-R0.1-SNAPSHOT").when(server).getBukkitVersion();
Bukkit.setServer(server);
}

@Test
void basicParse() {
assertThat(BukkitVersions.MINECRAFT, is(new IntVersion(1, 19, 4)));
assertThat(BukkitVersions.CRAFTBUKKIT_PACKAGE, is(BukkitVersions.MINECRAFT));
}

@ParameterizedTest
@ValueSource(strings = {
"org.bukkit.craftbukkit.v1_5_RV",
"org.bukkit.craftbukkit.1_4_5",
"invalid string",
"1.4.5-R1.0"
})
void invalidCbPackage(String packageName) {
assertThat(
"Invalid package must return Minecraft version",
BukkitVersions.parseCraftbukkitVersion(packageName),
is(BukkitVersions.MINECRAFT));
}

@ParameterizedTest
@MethodSource("getMcVersions")
void parseMcVersion(String version, Version expected) {
assertThat(
"Version must be parsed from Bukkit version",
BukkitVersions.parseMinecraftVersion(version),
is(expected));
}

private static Collection<Arguments> getMcVersions() {
return List.of(
Arguments.of("1.4.5-R1.0", new IntVersion(1, 4, 5)),
Arguments.of("1.19.4-R0.1-SNAPSHOT", new IntVersion(1, 19, 4)),
Arguments.of("1.20-R0.1-SNAPSHOT", new IntVersion(1, 20, 0)),
Arguments.of("unknown-format+1.10.3-R01 Cool Server Edition", new StringVersion("unknown")),
Arguments.of("1.10.3+coolserver-R0.1-SNAPSHOT", new IntVersion(1, 10, 3)),
Arguments.of("weirdserver+1.10.3-R0.1-SNAPSHOT", new StringVersion("weirdserver")),
Arguments.of("weirdserver-1.10.3-R0.1-SNAPSHOT", new StringVersion("weirdserver")),
Arguments.of("weirdserver+1.10.3", new StringVersion("weirdserver")),
Arguments.of("weirdserver1_10_R3", new StringVersion("weirdserver1_10_R3"))
);
}

@ParameterizedTest
@MethodSource("getCbVersions")
void parseCbVersion(String version, Version expected) {
assertThat(
"Version must be parsed from Craftbukkit package",
BukkitVersions.parseCraftbukkitVersion(version),
is(expected));
}

private static Collection<Arguments> getCbVersions() {
return List.of(
Arguments.of("org.bukkit.craftbukkit.v1_5_R1.other.package", new IntVersion(1, 5, 1)),
Arguments.of("org.bukkit.craftbukkit.v1_5_R1", new IntVersion(1, 5, 1)),
Arguments.of("org.bukkit.craftbukkit.v18_19_R50", new IntVersion(18, 19, 50)),
Arguments.of("org.bukkit.craftbukkit.v1_20_R2", new IntVersion(1, 20, 2))
);
}

}

0 comments on commit 624e49e

Please sign in to comment.