diff --git a/build.gradle b/build.gradle index 1376862b..2da92674 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ plugins { id 'java' } -group 'net.william278' +group 'org.inksnow.husk' version "$ext.plugin_version${versionMetadata()}" description "$ext.plugin_description" defaultTasks 'licenseFormat', 'build' @@ -28,30 +28,12 @@ ext { publishing { repositories { - if (System.getenv("RELEASES_MAVEN_USERNAME") != null) { - maven { - name = "william278-releases" - url = "https://repo.william278.net/releases" - credentials { - username = System.getenv("RELEASES_MAVEN_USERNAME") - password = System.getenv("RELEASES_MAVEN_PASSWORD") - } - authentication { - basic(BasicAuthentication) - } - } - } - if (System.getenv("SNAPSHOTS_MAVEN_USERNAME") != null) { - maven { - name = "william278-snapshots" - url = "https://repo.william278.net/snapshots" - credentials { - username = System.getenv("SNAPSHOTS_MAVEN_USERNAME") - password = System.getenv("SNAPSHOTS_MAVEN_PASSWORD") - } - authentication { - basic(BasicAuthentication) - } + maven { + name = 'husk-release' + url = findProperty("repository.huskrelease.url") + credentials { + username = findProperty("repository.huskrelease.username") + password = findProperty("repository.huskrelease.password") } } } @@ -63,23 +45,26 @@ allprojects { apply plugin: 'java' compileJava.options.encoding = 'UTF-8' - compileJava.options.release.set 17 + compileJava.options.release.set 8 + javadoc.options.encoding = 'UTF-8' javadoc.options.addStringOption('Xdoclint:none', '-quiet') repositories { mavenLocal() mavenCentral() - maven { url 'https://repo.william278.net/releases/' } + maven { + url findProperty('repository.huskpublic.url') + credentials { + username = findProperty('repository.huskpublic.username') + password = findProperty('repository.huskpublic.password') + } + } + maven { url 'https://r.irepo.space/maven/' } maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } maven { url "https://repo.dmulloy2.net/repository/public/" } maven { url 'https://repo.codemc.io/repository/maven-public/' } - maven { url 'https://repo.minebench.de/' } - maven { url 'https://repo.alessiodp.com/releases/' } - maven { url 'https://jitpack.io' } - maven { url 'https://mvn-repo.arim.space/lesser-gpl3/' } - maven { url 'https://libraries.minecraft.net/' } } dependencies { @@ -148,7 +133,7 @@ subprojects { if (['common'].contains(project.name)) { publications { mavenJavaCommon(MavenPublication) { - groupId = 'net.william278.husksync' + groupId = 'org.inksnow.husk.husksync' artifactId = 'husksync-common' version = "$rootProject.version" artifact shadowJar @@ -161,7 +146,7 @@ subprojects { if (['bukkit'].contains(project.name)) { publications { mavenJavaBukkit(MavenPublication) { - groupId = 'net.william278.husksync' + groupId = 'org.inksnow.husk.husksync' artifactId = 'husksync-bukkit' version = "$rootProject.version" artifact shadowJar @@ -174,7 +159,7 @@ subprojects { if (['fabric'].contains(project.name)) { publications { mavenJavaFabric(MavenPublication) { - groupId = 'net.william278.husksync' + groupId = 'org.inksnow.husk.husksync' artifactId = 'husksync-fabric' version = "$rootProject.version+${fabric_minecraft_version}" artifact remapJar diff --git a/bukkit/build.gradle b/bukkit/build.gradle index 1a8465c0..a77b04b9 100644 --- a/bukkit/build.gradle +++ b/bukkit/build.gradle @@ -1,15 +1,22 @@ dependencies { implementation project(path: ':common') - implementation 'net.william278.uniform:uniform-bukkit:1.2.1' + implementation 'org.slf4j:slf4j-api:1.7.36' + implementation 'org.inksnow.husk.uniform:uniform-bukkit:1.2.1-ad25f16' implementation 'net.william278:mpdbdataconverter:1.0.1' - implementation 'net.william278:hsldataconverter:1.0' + implementation 'org.inksnow.husk:hsldataconverter:1.0' implementation 'net.william278:mapdataapi:1.0.3' implementation 'org.bstats:bstats-bukkit:3.0.2' implementation 'net.kyori:adventure-platform-bukkit:4.3.4' implementation 'dev.triumphteam:triumph-gui:3.1.10' implementation 'space.arim.morepaperlib:morepaperlib:0.4.4' implementation 'de.tr7zw:item-nbt-api:2.13.2' + implementation 'com.google.guava:guava:33.2.1-jre' + implementation ("redis.clients:jedis:$jedis_version") { + exclude group: 'org.slf4j', module: 'slf4j-api' + } + implementation "org.xerial.snappy:snappy-java:$snappy_version" + implementation "org.mongodb:mongodb-driver-sync:$mongodb_driver_version" compileOnly 'org.spigotmc:spigot-api:1.17.1-R0.1-SNAPSHOT' compileOnly 'com.github.retrooper.packetevents:spigot:2.3.0' @@ -17,24 +24,27 @@ dependencies { compileOnly 'org.projectlombok:lombok:1.18.34' compileOnly 'commons-io:commons-io:2.16.1' compileOnly 'org.json:json:20240303' - compileOnly 'net.william278:minedown:1.8.2' - compileOnly 'de.exlll:configlib-yaml:4.5.0' - compileOnly 'com.zaxxer:HikariCP:5.1.0' - compileOnly 'net.william278:DesertWell:2.0.4' + compileOnly 'de.themoep:minedown-adventure:1.7.3-SNAPSHOT' + compileOnly 'org.inksnow.husk:configlib-yaml:4.5.4' + compileOnly 'com.zaxxer:HikariCP:4.0.3' + compileOnly 'org.inksnow.husk:desertwell:2.0.5-9962d59:all' compileOnly 'net.william278:AdvancementAPI:97a9583413' - compileOnly "redis.clients:jedis:$jedis_version" annotationProcessor 'org.projectlombok:lombok:1.18.34' } shadowJar { - dependencies { - exclude(dependency('com.mojang:brigadier')) - } + mergeServiceFiles() + + relocate 'org.inksnow.cputil', 'net.william278.husksync.libraries.cputil' + relocate 'org.slf4j', 'net.william278.husksync.libraries.slf4j' + relocate 'org.objectweb.asm', 'net.william278.husksync.libraries.asm' + relocate 'org.apache.commons.io', 'net.william278.husksync.libraries.commons.io' relocate 'org.apache.commons.text', 'net.william278.husksync.libraries.commons.text' relocate 'org.apache.commons.lang3', 'net.william278.husksync.libraries.commons.lang3' relocate 'com.google.gson', 'net.william278.husksync.libraries.gson' + relocate 'com.google.common', 'net.william278.husksync.libraries.guava' relocate 'com.fatboyindustrial', 'net.william278.husksync.libraries' relocate 'de.themoep', 'net.william278.husksync.libraries' relocate 'org.jetbrains', 'net.william278.husksync.libraries' @@ -54,6 +64,4 @@ shadowJar { relocate 'dev.triumphteam.gui', 'net.william278.husksync.libraries.triumphgui' relocate 'space.arim.morepaperlib', 'net.william278.husksync.libraries.paperlib' relocate 'de.tr7zw.changeme.nbtapi', 'net.william278.husksync.libraries.nbtapi' - - minimize() -} \ No newline at end of file +} diff --git a/bukkit/src/main/java/net/william278/husksync/BukkitHuskSync.java b/bukkit/src/main/java/net/william278/husksync/BukkitHuskSync.java index a75e16c2..4b9993ef 100644 --- a/bukkit/src/main/java/net/william278/husksync/BukkitHuskSync.java +++ b/bukkit/src/main/java/net/william278/husksync/BukkitHuskSync.java @@ -57,6 +57,8 @@ import net.william278.husksync.util.BukkitLegacyConverter; import net.william278.husksync.util.BukkitMapPersister; import net.william278.husksync.util.BukkitTask; import net.william278.husksync.util.LegacyConverter; +import net.william278.husksync.util.StringUtil; +import net.william278.husksync.util.ref.RefUtil; import net.william278.uniform.Uniform; import net.william278.uniform.bukkit.BukkitUniform; import org.bstats.bukkit.Metrics; @@ -64,6 +66,9 @@ import org.bukkit.entity.Player; import org.bukkit.map.MapView; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; +import org.inksnow.cputil.AuroraCputil; +import org.inksnow.cputil.AuroraUrl; +import org.inksnow.cputil.logger.AuroraLoggerFactory; import org.jetbrains.annotations.NotNull; import space.arim.morepaperlib.MorePaperLib; import space.arim.morepaperlib.scheduling.AsynchronousScheduler; @@ -81,10 +86,21 @@ import java.util.stream.Collectors; public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.Supplier, BukkitEventDispatcher, BukkitMapPersister { + static { + AuroraLoggerFactory.instance().nameMapping(it -> { + int split = it.lastIndexOf('.'); + if (split != -1) { + return "HuskSync-Aurora " + it.substring(split + 1); + } else { + return "HuskSync-Aurora " + it; + } + }); + } + /** - * Metrics ID for HuskSync on Bukkit. + * Metrics ID for HuskSync-Aurora. */ - private static final int METRICS_ID = 13140; + private static final int METRICS_ID = 23157; private static final String PLATFORM_TYPE_ID = "bukkit"; private final TreeMap> serializers = Maps.newTreeMap( @@ -151,15 +167,21 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S // Prepare serializers initialize("data serializers", (plugin) -> { - registerSerializer(Identifier.PERSISTENT_DATA, new BukkitSerializer.PersistentData(this)); + if (RefUtil.bootstrapVirtualMethod("Lorg/bukkit/persistence/PersistentDataHolder;getPersistentDataContainer()Lorg/bukkit/persistence/PersistentDataContainer;") != null) { + registerSerializer(Identifier.PERSISTENT_DATA, new BukkitSerializer.PersistentData(this)); + } registerSerializer(Identifier.INVENTORY, new BukkitSerializer.Inventory(this)); registerSerializer(Identifier.ENDER_CHEST, new BukkitSerializer.EnderChest(this)); registerSerializer(Identifier.ADVANCEMENTS, new BukkitSerializer.Advancements(this)); - registerSerializer(Identifier.STATISTICS, new Serializer.Json<>(this, BukkitData.Statistics.class)); + if (RefUtil.bootstrapStaticFieldGet("Lorg/bukkit/Registry;STATISTIC:Lorg/bukkit/Registry;") != null) { + registerSerializer(Identifier.STATISTICS, new Serializer.Json<>(this, BukkitData.Statistics.class)); + } registerSerializer(Identifier.POTION_EFFECTS, new BukkitSerializer.PotionEffects(this)); registerSerializer(Identifier.GAME_MODE, new Serializer.Json<>(this, BukkitData.GameMode.class)); registerSerializer(Identifier.FLIGHT_STATUS, new Serializer.Json<>(this, BukkitData.FlightStatus.class)); - registerSerializer(Identifier.ATTRIBUTES, new Serializer.Json<>(this, BukkitData.Attributes.class)); + if (RefUtil.bootstrapStaticFieldGet("Lorg/bukkit/Registry;ATTRIBUTE:Lorg/bukkit/Registry;") != null) { + registerSerializer(Identifier.ATTRIBUTES, new Serializer.Json<>(this, BukkitData.Attributes.class)); + } registerSerializer(Identifier.HEALTH, new Serializer.Json<>(this, BukkitData.Health.class)); registerSerializer(Identifier.HUNGER, new Serializer.Json<>(this, BukkitData.Hunger.class)); registerSerializer(Identifier.EXPERIENCE, new Serializer.Json<>(this, BukkitData.Experience.class)); @@ -178,11 +200,24 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S // Initialize the database initialize(getSettings().getDatabase().getType().getDisplayName() + " database connection", (plugin) -> { - this.database = switch (settings.getDatabase().getType()) { - case MYSQL, MARIADB -> new MySqlDatabase(this); - case POSTGRES -> new PostgresDatabase(this); - case MONGO -> new MongoDbDatabase(this); - }; + switch (settings.getDatabase().getType()) { + case MYSQL: + case MARIADB: { + this.database = new MySqlDatabase(this); + break; + } + case POSTGRES: { + this.database = new PostgresDatabase(this); + break; + } + case MONGO: { + this.database = new MongoDbDatabase(this); + break; + } + default: { + // do nothing + } + } this.database.initialize(); }); @@ -295,14 +330,14 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S // Register bStats metrics public void registerMetrics(int metricsId) { - if (!getPluginVersion().getMetadata().isBlank()) { + if (!StringUtil.isBlank(getPluginVersion().getMetadata())) { return; } try { new Metrics(this, metricsId); } catch (Throwable e) { - log(Level.WARNING, "Failed to register bStats metrics (%s)".formatted(e.getMessage())); + log(Level.WARNING, "Failed to register bStats metrics (" + e.getMessage() + ")"); } } diff --git a/bukkit/src/main/java/net/william278/husksync/data/BukkitData.java b/bukkit/src/main/java/net/william278/husksync/data/BukkitData.java index 539597ce..eb86216f 100644 --- a/bukkit/src/main/java/net/william278/husksync/data/BukkitData.java +++ b/bukkit/src/main/java/net/william278/husksync/data/BukkitData.java @@ -33,6 +33,7 @@ import net.william278.husksync.HuskSync; import net.william278.husksync.adapter.Adaptable; import net.william278.husksync.config.Settings.SynchronizationSettings.AttributeSettings; import net.william278.husksync.user.BukkitUser; +import net.william278.husksync.util.ref.RefUtil; import org.bukkit.*; import org.bukkit.advancement.AdvancementProgress; import org.bukkit.attribute.AttributeInstance; @@ -92,8 +93,8 @@ public abstract class BukkitData implements Data { stack.hasItemMeta() && Objects.requireNonNull(stack.getItemMeta()).hasEnchants() ? stack.getItemMeta().getEnchants().keySet().stream() .map(enchantment -> enchantment.getKey().getKey()) - .toList() - : List.of() + .collect(Collectors.toList()) + : Collections.emptyList() ) : null) .toArray(Stack[]::new); } @@ -118,7 +119,8 @@ public abstract class BukkitData implements Data { @Override public boolean equals(Object obj) { - if (obj instanceof BukkitData.Items items) { + if (obj instanceof BukkitData.Items) { + BukkitData.Items items = (BukkitData.Items) obj; return Arrays.equals(contents, items.getContents()); } return false; @@ -189,7 +191,7 @@ public abstract class BukkitData implements Data { @NotNull public static BukkitData.Items.EnderChest adapt(@NotNull Collection items) { - return adapt(items.toArray(ItemStack[]::new)); + return adapt(items.toArray(new ItemStack[0])); } @NotNull @@ -212,7 +214,7 @@ public abstract class BukkitData implements Data { @NotNull public static ItemArray adapt(@NotNull Collection drops) { - return new ItemArray(drops.toArray(ItemStack[]::new)); + return new ItemArray(drops.toArray(new ItemStack[0])); } @NotNull @@ -238,26 +240,28 @@ public abstract class BukkitData implements Data { @NotNull public static BukkitData.PotionEffects from(@NotNull Collection sei) { - return new BukkitData.PotionEffects(Lists.newArrayList(sei.stream().filter(e -> !e.isAmbient()).toList())); - + return new BukkitData.PotionEffects(Lists.newArrayList( + sei.stream().filter(e -> !e.isAmbient()) + .collect(Collectors.toList()) + )); } @NotNull public static BukkitData.PotionEffects adapt(@NotNull Collection effects) { return from(effects.stream() .map(effect -> { - final PotionEffectType type = matchEffectType(effect.type()); + final PotionEffectType type = matchEffectType(effect.getType()); return type != null ? new PotionEffect( type, - effect.duration(), - effect.amplifier(), + effect.getDuration(), + effect.getAmplifier(), effect.isAmbient(), - effect.showParticles(), - effect.hasIcon() + effect.isShowParticles(), + effect.isHasIcon() ) : null; }) .filter(Objects::nonNull) - .toList()); + .collect(Collectors.toList())); } @NotNull @@ -290,7 +294,7 @@ public abstract class BukkitData implements Data { potionEffect.hasParticles(), potionEffect.hasIcon() )) - .toList(); + .collect(Collectors.toList()); } } @@ -335,16 +339,19 @@ public abstract class BukkitData implements Data { final Optional record = completed.stream() .filter(r -> r.getKey().equals(advancement.getKey().toString())) .findFirst(); - if (record.isEmpty()) { - this.setAdvancement(plugin, advancement, player, user, List.of(), progress.getAwardedCriteria()); + if (!record.isPresent()) { + this.setAdvancement(plugin, advancement, player, user, Collections.emptyList(), progress.getAwardedCriteria()); return; } final Map criteria = record.get().getCompletedCriteria(); this.setAdvancement( plugin, advancement, player, user, - criteria.keySet().stream().filter(key -> !progress.getAwardedCriteria().contains(key)).toList(), - progress.getAwardedCriteria().stream().filter(key -> !criteria.containsKey(key)).toList() + criteria.keySet().stream() + .filter(key -> !progress.getAwardedCriteria().contains(key)) + .collect(Collectors.toList()), + progress.getAwardedCriteria().stream().filter(key -> !criteria.containsKey(key)) + .collect(Collectors.toList()) ); })); } @@ -426,7 +433,7 @@ public abstract class BukkitData implements Data { public void apply(@NotNull BukkitUser user, @NotNull BukkitHuskSync plugin) throws IllegalStateException { try { final org.bukkit.Location location = new org.bukkit.Location( - Bukkit.getWorld(world.name()), x, y, z, yaw, pitch + Bukkit.getWorld(world.getName()), x, y, z, yaw, pitch ); user.getPlayer().teleport(location); } catch (Throwable e) { @@ -457,10 +464,25 @@ public abstract class BukkitData implements Data { items = Maps.newHashMap(), entities = Maps.newHashMap(); Registry.STATISTIC.forEach(id -> { switch (id.getType()) { - case UNTYPED -> addStatistic(player, id, generic); - case BLOCK -> addMaterialStatistic(player, id, blocks, true); - case ITEM -> addMaterialStatistic(player, id, items, false); - case ENTITY -> addEntityStatistic(player, id, entities); + case UNTYPED: { + addStatistic(player, id, generic); + break; + } + case BLOCK: { + addMaterialStatistic(player, id, blocks, true); + break; + } + case ITEM: { + addMaterialStatistic(player, id, items, false); + break; + } + case ENTITY: { + addEntityStatistic(player, id, entities); + break; + } + default: { + // do nothing + } } }); return new BukkitData.Statistics(generic, blocks, items, entities); @@ -527,9 +549,22 @@ public abstract class BukkitData implements Data { try { switch (type) { - case UNTYPED -> player.setStatistic(stat, value); - case BLOCK, ITEM -> player.setStatistic(stat, Objects.requireNonNull(matchMaterial(key[0])), value); - case ENTITY -> player.setStatistic(stat, Objects.requireNonNull(matchEntityType(key[0])), value); + case UNTYPED: { + player.setStatistic(stat, value); + break; + } + case BLOCK: + case ITEM: { + player.setStatistic(stat, Objects.requireNonNull(matchMaterial(key[0])), value); + break; + } + case ENTITY: { + player.setStatistic(stat, Objects.requireNonNull(matchEntityType(key[0])), value); + break; + } + default: { + // do nothing + } } } catch (Throwable ignored) { } @@ -591,7 +626,7 @@ public abstract class BukkitData implements Data { } public Optional getAttribute(@NotNull org.bukkit.attribute.Attribute id) { - return attributes.stream().filter(attribute -> attribute.name().equals(id.getKey().toString())).findFirst(); + return attributes.stream().filter(attribute -> attribute.getName().equals(id.getKey().toString())).findFirst(); } @SuppressWarnings("unused") @@ -648,10 +683,10 @@ public abstract class BukkitData implements Data { if (instance == null) { return; } - instance.setBaseValue(attribute == null ? instance.getDefaultValue() : attribute.baseValue()); + instance.setBaseValue(attribute == null ? instance.getDefaultValue() : attribute.getBaseValue()); instance.getModifiers().forEach(instance::removeModifier); if (attribute != null) { - attribute.modifiers().stream() + attribute.getModifiers().stream() .filter(mod -> instance.getModifiers().stream().map(AttributeModifier::getName) .noneMatch(n -> n.equals(mod.name()))) .distinct() @@ -722,19 +757,19 @@ public abstract class BukkitData implements Data { } /** - * @deprecated Use {@link #from(double, double, boolean)} instead + * @deprecated Use {@link #from(double, double, boolean)} instead, since 3.5.4 */ @NotNull - @Deprecated(since = "3.5.4") + @Deprecated public static BukkitData.Health from(double health, double scale) { return from(health, scale, false); } /** - * @deprecated Use {@link #from(double, double, boolean)} instead + * @deprecated Use {@link #from(double, double, boolean)} instead, since 3.5 */ @NotNull - @Deprecated(forRemoval = true, since = "3.5") + @Deprecated public static BukkitData.Health from(double health, @SuppressWarnings("unused") double max, double scale) { return from(health, scale, false); } @@ -757,7 +792,7 @@ public abstract class BukkitData implements Data { try { player.setHealth(Math.min(health, player.getMaxHealth())); } catch (Throwable e) { - plugin.log(Level.WARNING, "Error setting %s's health to %s".formatted(player.getName(), health), e); + plugin.log(Level.WARNING, "Error setting " + player.getName() + "'s health to " + health, e); } // Set health scale @@ -766,7 +801,7 @@ public abstract class BukkitData implements Data { player.setHealthScale(scale); player.setHealthScaled(isHealthScaled); } catch (Throwable e) { - plugin.log(Level.WARNING, "Error setting %s's health scale to %s".formatted(player.getName(), scale), e); + plugin.log(Level.WARNING, "Error setting " + player.getName() + "'s health scale to " + scale, e); } } @@ -855,7 +890,7 @@ public abstract class BukkitData implements Data { } @NotNull - @Deprecated(forRemoval = true, since = "3.5") + @Deprecated @SuppressWarnings("unused") public static BukkitData.GameMode from(@NotNull String gameMode, boolean allowFlight, boolean isFlying) { return new BukkitData.GameMode(gameMode); diff --git a/bukkit/src/main/java/net/william278/husksync/data/BukkitSerializer.java b/bukkit/src/main/java/net/william278/husksync/data/BukkitSerializer.java index 7f900165..e9c4f058 100644 --- a/bukkit/src/main/java/net/william278/husksync/data/BukkitSerializer.java +++ b/bukkit/src/main/java/net/william278/husksync/data/BukkitSerializer.java @@ -33,6 +33,7 @@ import net.william278.husksync.BukkitHuskSync; import net.william278.husksync.HuskSync; import net.william278.husksync.adapter.Adaptable; import net.william278.husksync.api.HuskSyncAPI; +import net.william278.husksync.util.ref.RefUtil; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.ApiStatus; @@ -127,13 +128,13 @@ public class BukkitSerializer { @Nullable default ItemStack[] getItems(@NotNull ReadWriteNBT tag, @NotNull Version mcVersion) { if (mcVersion.compareTo(getPlugin().getMinecraftVersion()) < 0) { - return upgradeItemStacks((NBTCompound) tag, mcVersion); + return private$upgradeItemStacks((NBTCompound) tag, mcVersion); } return NBT.itemStackArrayFromNBT(tag); } @NotNull - private ItemStack @NotNull [] upgradeItemStacks(@NotNull NBTCompound itemsNbt, @NotNull Version mcVersion) { + default ItemStack @NotNull [] private$upgradeItemStacks(@NotNull NBTCompound itemsNbt, @NotNull Version mcVersion) { final ReadWriteNBTCompoundList items = itemsNbt.getCompoundList("items"); final ItemStack[] itemStacks = new ItemStack[itemsNbt.getInteger("size")]; for (int i = 0; i < items.size(); i++) { @@ -142,7 +143,7 @@ public class BukkitSerializer { continue; } try { - itemStacks[i] = NBT.itemStackFromNBT(upgradeItemData(items.get(i), mcVersion)); + itemStacks[i] = NBT.itemStackFromNBT(private$upgradeItemData(items.get(i), mcVersion)); } catch (Throwable e) { itemStacks[i] = new ItemStack(Material.AIR); } @@ -151,23 +152,47 @@ public class BukkitSerializer { } @NotNull - private ReadWriteNBT upgradeItemData(@NotNull ReadWriteNBT tag, @NotNull Version mcVersion) + default ReadWriteNBT private$upgradeItemData(@NotNull ReadWriteNBT tag, @NotNull Version mcVersion) throws NoSuchFieldException, IllegalAccessException { - return DataFixerUtil.fixUpItemData(tag, getDataVersion(mcVersion), DataFixerUtil.getCurrentVersion()); + return DataFixerUtil.fixUpItemData(tag, private$getDataVersion(mcVersion), DataFixerUtil.getCurrentVersion()); } - private int getDataVersion(@NotNull Version mcVersion) { - return switch (mcVersion.toStringWithoutMetadata()) { - case "1.16", "1.16.1", "1.16.2", "1.16.3", "1.16.4", "1.16.5" -> DataFixerUtil.VERSION1_16_5; - case "1.17", "1.17.1" -> DataFixerUtil.VERSION1_17_1; - case "1.18", "1.18.1", "1.18.2" -> DataFixerUtil.VERSION1_18_2; - case "1.19", "1.19.1", "1.19.2" -> DataFixerUtil.VERSION1_19_2; - case "1.20", "1.20.1", "1.20.2" -> DataFixerUtil.VERSION1_20_2; - case "1.20.3", "1.20.4" -> DataFixerUtil.VERSION1_20_4; - case "1.20.5", "1.20.6" -> DataFixerUtil.VERSION1_20_5; - case "1.21" -> DataFixerUtil.VERSION1_21; - default -> DataFixerUtil.getCurrentVersion(); - }; + default int private$getDataVersion(@NotNull Version mcVersion) { + switch (mcVersion.toStringWithoutMetadata()) { + case "1.12": case "1.12.1": case "1.12.2": + case "1.13": case "1.13.1": case "1.13.2": + case "1.14": case "1.14.1": case "1.14.2": case "1.14.3": case "1.14.4": + case "1.15": case "1.15.1": case "1.15.2": { + return DataFixerUtil.VERSION1_12_2; + } + case "1.16": case "1.16.1": case "1.16.2": case "1.16.3": case "1.16.4": case "1.16.5": { + return DataFixerUtil.VERSION1_16_5; + } + case "1.17": case "1.17.1": { + return DataFixerUtil.VERSION1_17_1; + } + case "1.18": case "1.18.1": case "1.18.2": { + return DataFixerUtil.VERSION1_18_2; + } + case "1.19": case "1.19.1": case "1.19.2": { + return DataFixerUtil.VERSION1_19_2; + } + case "1.20": case "1.20.1": case "1.20.2": { + return DataFixerUtil.VERSION1_20_2; + } + case "1.20.3": case "1.20.4": { + return DataFixerUtil.VERSION1_20_4; + } + case "1.20.5": case "1.20.6": { + return DataFixerUtil.VERSION1_20_5; + } + case "1.21": { + return DataFixerUtil.VERSION1_21; + } + default: { + return DataFixerUtil.getCurrentVersion(); + } + } } @NotNull @@ -176,7 +201,7 @@ public class BukkitSerializer { public static class PotionEffects extends BukkitSerializer implements Serializer { - private static final TypeToken> TYPE = new TypeToken<>() { + private static final TypeToken> TYPE = new TypeToken>() { }; public PotionEffects(@NotNull HuskSync plugin) { @@ -200,7 +225,7 @@ public class BukkitSerializer { public static class Advancements extends BukkitSerializer implements Serializer { - private static final TypeToken> TYPE = new TypeToken<>() { + private static final TypeToken> TYPE = new TypeToken>() { }; public Advancements(@NotNull HuskSync plugin) { @@ -241,9 +266,9 @@ public class BukkitSerializer { } /** - * @deprecated Use {@link Serializer.Json} in the common module instead + * @deprecated Use {@link Serializer.Json} in the common module instead, since 2.6 */ - @Deprecated(since = "2.6") + @Deprecated public class Json extends Serializer.Json { public Json(@NotNull HuskSync plugin, @NotNull Class type) { diff --git a/bukkit/src/main/java/net/william278/husksync/data/BukkitUserDataHolder.java b/bukkit/src/main/java/net/william278/husksync/data/BukkitUserDataHolder.java index 9eb93aaf..54f3ef94 100644 --- a/bukkit/src/main/java/net/william278/husksync/data/BukkitUserDataHolder.java +++ b/bukkit/src/main/java/net/william278/husksync/data/BukkitUserDataHolder.java @@ -32,22 +32,22 @@ public interface BukkitUserDataHolder extends UserDataHolder { @Override default Optional getData(@NotNull Identifier id) { if (!id.isCustom()) { - return switch (id.getKeyValue()) { - case "inventory" -> getInventory(); - case "ender_chest" -> getEnderChest(); - case "potion_effects" -> getPotionEffects(); - case "advancements" -> getAdvancements(); - case "location" -> getLocation(); - case "statistics" -> getStatistics(); - case "health" -> getHealth(); - case "hunger" -> getHunger(); - case "attributes" -> getAttributes(); - case "experience" -> getExperience(); - case "game_mode" -> getGameMode(); - case "flight_status" -> getFlightStatus(); - case "persistent_data" -> getPersistentData(); - default -> throw new IllegalStateException(String.format("Unexpected data type: %s", id)); - }; + switch (id.getKeyValue()) { + case "inventory": return getInventory(); + case "ender_chest": return getEnderChest(); + case "potion_effects": return getPotionEffects(); + case "advancements": return getAdvancements(); + case "location": return getLocation(); + case "statistics": return getStatistics(); + case "health": return getHealth(); + case "hunger": return getHunger(); + case "attributes": return getAttributes(); + case "experience": return getExperience(); + case "game_mode": return getGameMode(); + case "flight_status": return getFlightStatus(); + case "persistent_data": return getPersistentData(); + default: throw new IllegalStateException(String.format("Unexpected data type: %s", id)); + } } return Optional.ofNullable(getCustomDataStore().get(id)); } @@ -154,9 +154,9 @@ public interface BukkitUserDataHolder extends UserDataHolder { Player getPlayer(); /** - * @deprecated Use {@link #getPlayer()} instead + * @deprecated Use {@link #getPlayer()} instead, since 3.6 */ - @Deprecated(since = "3.6") + @Deprecated @NotNull default Player getBukkitPlayer() { return getPlayer(); diff --git a/bukkit/src/main/java/net/william278/husksync/event/BukkitEventDispatcher.java b/bukkit/src/main/java/net/william278/husksync/event/BukkitEventDispatcher.java index 28a78eae..e23c5d95 100644 --- a/bukkit/src/main/java/net/william278/husksync/event/BukkitEventDispatcher.java +++ b/bukkit/src/main/java/net/william278/husksync/event/BukkitEventDispatcher.java @@ -30,7 +30,7 @@ public interface BukkitEventDispatcher extends EventDispatcher { @Override default boolean fireIsCancelled(@NotNull T event) { Bukkit.getPluginManager().callEvent((org.bukkit.event.Event) event); - return event instanceof Cancellable cancellable && cancellable.isCancelled(); + return event instanceof Cancellable && ((Cancellable) event).isCancelled(); } @NotNull diff --git a/bukkit/src/main/java/net/william278/husksync/listener/BukkitEventListener.java b/bukkit/src/main/java/net/william278/husksync/listener/BukkitEventListener.java index 7bdea7d7..16eb7ae2 100644 --- a/bukkit/src/main/java/net/william278/husksync/listener/BukkitEventListener.java +++ b/bukkit/src/main/java/net/william278/husksync/listener/BukkitEventListener.java @@ -23,6 +23,7 @@ import net.william278.husksync.BukkitHuskSync; import net.william278.husksync.data.BukkitData; import net.william278.husksync.user.BukkitUser; import net.william278.husksync.user.OnlineUser; +import net.william278.husksync.util.MaterialUtil; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -82,7 +83,7 @@ public class BukkitEventListener extends EventListener implements BukkitJoinEven public void handlePlayerQuit(@NotNull BukkitUser bukkitUser) { final Player player = bukkitUser.getPlayer(); final ItemStack itemOnCursor = player.getItemOnCursor(); - if (!bukkitUser.isLocked() && !itemOnCursor.getType().isAir()) { + if (!bukkitUser.isLocked() && !MaterialUtil.isAir(itemOnCursor.getType())) { player.setItemOnCursor(null); player.getWorld().dropItem(player.getLocation(), itemOnCursor); plugin.debug("Dropped " + itemOnCursor + " for " + player.getName() + " on quit"); diff --git a/bukkit/src/main/java/net/william278/husksync/listener/BukkitLockedEventListener.java b/bukkit/src/main/java/net/william278/husksync/listener/BukkitLockedEventListener.java index f20ea4a1..52a247a3 100644 --- a/bukkit/src/main/java/net/william278/husksync/listener/BukkitLockedEventListener.java +++ b/bukkit/src/main/java/net/william278/husksync/listener/BukkitLockedEventListener.java @@ -60,8 +60,8 @@ public class BukkitLockedEventListener implements LockedHandler, Listener { @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onProjectileLaunch(@NotNull ProjectileLaunchEvent event) { final Projectile projectile = event.getEntity(); - if (projectile.getShooter() instanceof Player player) { - cancelPlayerEvent(player.getUniqueId(), event); + if (projectile.getShooter() instanceof Player) { + cancelPlayerEvent(((Player) projectile.getShooter()).getUniqueId(), event); } } @@ -72,8 +72,8 @@ public class BukkitLockedEventListener implements LockedHandler, Listener { @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onPickupItem(@NotNull EntityPickupItemEvent event) { - if (event.getEntity() instanceof Player player) { - cancelPlayerEvent(player.getUniqueId(), event); + if (event.getEntity() instanceof Player) { + cancelPlayerEvent(event.getEntity().getUniqueId(), event); } } @@ -104,8 +104,8 @@ public class BukkitLockedEventListener implements LockedHandler, Listener { @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onInventoryOpen(@NotNull InventoryOpenEvent event) { - if (event.getPlayer() instanceof Player player) { - cancelPlayerEvent(player.getUniqueId(), event); + if (event.getPlayer() instanceof Player) { + cancelPlayerEvent(event.getPlayer().getUniqueId(), event); } } @@ -116,8 +116,8 @@ public class BukkitLockedEventListener implements LockedHandler, Listener { @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onPlayerTakeDamage(@NotNull EntityDamageEvent event) { - if (event.getEntity() instanceof Player player) { - cancelPlayerEvent(player.getUniqueId(), event); + if (event.getEntity() instanceof Player) { + cancelPlayerEvent(event.getEntity().getUniqueId(), event); } } diff --git a/bukkit/src/main/java/net/william278/husksync/listener/BukkitPacketEventsLockedPacketListener.java b/bukkit/src/main/java/net/william278/husksync/listener/BukkitPacketEventsLockedPacketListener.java index dc66a8c4..50597a53 100644 --- a/bukkit/src/main/java/net/william278/husksync/listener/BukkitPacketEventsLockedPacketListener.java +++ b/bukkit/src/main/java/net/william278/husksync/listener/BukkitPacketEventsLockedPacketListener.java @@ -25,6 +25,7 @@ import com.github.retrooper.packetevents.event.PacketListenerPriority; import com.github.retrooper.packetevents.event.PacketReceiveEvent; import com.github.retrooper.packetevents.event.PacketSendEvent; import com.github.retrooper.packetevents.protocol.packettype.PacketType; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder; import net.william278.husksync.BukkitHuskSync; @@ -59,7 +60,7 @@ public class BukkitPacketEventsLockedPacketListener extends BukkitLockedEventLis private static class PlayerPacketAdapter extends PacketListenerAbstract { - private static final Set ALLOWED_PACKETS = Set.of( + private static final Set ALLOWED_PACKETS = ImmutableSet.of( PacketType.Play.Client.KEEP_ALIVE, PacketType.Play.Client.PONG, PacketType.Play.Client.PLUGIN_MESSAGE, // Connection packets PacketType.Play.Client.CHAT_MESSAGE, PacketType.Play.Client.CHAT_COMMAND, PacketType.Play.Client.CHAT_SESSION_UPDATE, // Chat / command packets PacketType.Play.Client.PLAYER_POSITION, PacketType.Play.Client.PLAYER_POSITION_AND_ROTATION, PacketType.Play.Client.PLAYER_ROTATION, // Movement packets @@ -79,10 +80,10 @@ public class BukkitPacketEventsLockedPacketListener extends BukkitLockedEventLis @Override public void onPacketReceive(PacketReceiveEvent event) { - if (!(event.getPacketType() instanceof PacketType.Play.Client client)) { + if (!(event.getPacketType() instanceof PacketType.Play.Client)) { return; } - if (!CANCEL_PACKETS.contains(client)) { + if (!CANCEL_PACKETS.contains((PacketType.Play.Client) event.getPacketType())) { return; } if (listener.cancelPlayerEvent(event.getUser().getUUID())) { @@ -92,10 +93,10 @@ public class BukkitPacketEventsLockedPacketListener extends BukkitLockedEventLis @Override public void onPacketSend(PacketSendEvent event) { - if (!(event.getPacketType() instanceof PacketType.Play.Client client)) { + if (!(event.getPacketType() instanceof PacketType.Play.Client)) { return; } - if (!CANCEL_PACKETS.contains(client)) { + if (!CANCEL_PACKETS.contains((PacketType.Play.Client) event.getPacketType())) { return; } if (listener.cancelPlayerEvent(event.getUser().getUUID())) { diff --git a/bukkit/src/main/java/net/william278/husksync/listener/BukkitProtocolLibLockedPacketListener.java b/bukkit/src/main/java/net/william278/husksync/listener/BukkitProtocolLibLockedPacketListener.java index eac661b0..04d2ae7a 100644 --- a/bukkit/src/main/java/net/william278/husksync/listener/BukkitProtocolLibLockedPacketListener.java +++ b/bukkit/src/main/java/net/william278/husksync/listener/BukkitProtocolLibLockedPacketListener.java @@ -24,6 +24,7 @@ import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.events.ListenerPriority; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketEvent; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import net.william278.husksync.BukkitHuskSync; import org.jetbrains.annotations.NotNull; @@ -50,7 +51,7 @@ public class BukkitProtocolLibLockedPacketListener extends BukkitLockedEventList private static class PlayerPacketAdapter extends PacketAdapter { // Packets we want the player to still be able to send/receiver to/from the server - private static final Set ALLOWED_PACKETS = Set.of( + private static final Set ALLOWED_PACKETS = ImmutableSet.of( Client.KEEP_ALIVE, Client.PONG, Client.CUSTOM_PAYLOAD, // Connection packets Client.CHAT_COMMAND, Client.CLIENT_COMMAND, Client.CHAT, Client.CHAT_SESSION_UPDATE, // Chat / command packets Client.POSITION, Client.POSITION_LOOK, Client.LOOK, // Movement packets diff --git a/bukkit/src/main/java/net/william278/husksync/migrator/LegacyMigrator.java b/bukkit/src/main/java/net/william278/husksync/migrator/LegacyMigrator.java index 3b9eff40..49183fcb 100644 --- a/bukkit/src/main/java/net/william278/husksync/migrator/LegacyMigrator.java +++ b/bukkit/src/main/java/net/william278/husksync/migrator/LegacyMigrator.java @@ -22,6 +22,7 @@ package net.william278.husksync.migrator; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.zaxxer.hikari.HikariDataSource; +import lombok.Value; import me.william278.husksync.bukkit.data.DataSerializer; import net.william278.hslmigrator.HSLConverter; import net.william278.husksync.HuskSync; @@ -43,6 +44,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.regex.Pattern; +import java.util.stream.Collectors; import static net.william278.husksync.config.Settings.DatabaseSettings; @@ -95,14 +97,12 @@ public class LegacyMigrator extends Migrator { plugin.log(Level.INFO, "Downloading raw data from the legacy database (this might take a while)..."); final List dataToMigrate = Lists.newArrayList(); try (final Connection connection = connectionPool.getConnection()) { - try (final PreparedStatement statement = connection.prepareStatement(""" - SELECT `uuid`, `username`, `inventory`, `ender_chest`, `health`, `max_health`, `health_scale`, `hunger`, `saturation`, `saturation_exhaustion`, `selected_slot`, `status_effects`, `total_experience`, `exp_level`, `exp_progress`, `game_mode`, `statistics`, `is_flying`, `advancements`, `location` - FROM `%source_players_table%` - INNER JOIN `%source_data_table%` - ON `%source_players_table%`.`id` = `%source_data_table%`.`player_id` - WHERE `username` IS NOT NULL; - """.replaceAll(Pattern.quote("%source_players_table%"), sourcePlayersTable) - .replaceAll(Pattern.quote("%source_data_table%"), sourceDataTable))) { + try (final PreparedStatement statement = connection.prepareStatement( + "SELECT `uuid`, `username`, `inventory`, `ender_chest`, `health`, `max_health`, `health_scale`, `hunger`, `saturation`, `saturation_exhaustion`, `selected_slot`, `status_effects`, `total_experience`, `exp_level`, `exp_progress`, `game_mode`, `statistics`, `is_flying`, `advancements`, `location` " + + "FROM `" + sourcePlayersTable + "` " + + "INNER JOIN `" + sourceDataTable + "` " + + "ON `" + sourcePlayersTable + "`.`id` = `" + sourceDataTable + "`.`player_id` " + + "WHERE `username` IS NOT NULL;")) { try (final ResultSet resultSet = statement.executeQuery()) { int playersMigrated = 0; while (resultSet.next()) { @@ -142,11 +142,11 @@ public class LegacyMigrator extends Migrator { final AtomicInteger playersConverted = new AtomicInteger(); dataToMigrate.forEach(data -> { final DataSnapshot.Packed convertedData = data.toUserData(hslConverter, plugin); - plugin.getDatabase().ensureUser(data.user()); + plugin.getDatabase().ensureUser(data.getUser()); try { - plugin.getDatabase().addSnapshot(data.user(), convertedData); + plugin.getDatabase().addSnapshot(data.getUser(), convertedData); } catch (Throwable e) { - plugin.log(Level.SEVERE, "Failed to migrate legacy data for " + data.user().getUsername() + ": " + e.getMessage()); + plugin.log(Level.SEVERE, "Failed to migrate legacy data for " + data.getUser().getUsername() + ": " + e.getMessage()); return; } @@ -167,41 +167,53 @@ public class LegacyMigrator extends Migrator { @Override public void handleConfigurationCommand(@NotNull String[] args) { if (args.length == 2) { - if (switch (args[0].toLowerCase(Locale.ENGLISH)) { - case "host" -> { + boolean $yield; + switch (args[0].toLowerCase(Locale.ENGLISH)) { + case "host": { this.sourceHost = args[1]; - yield true; + $yield = true; + break; } - case "port" -> { + case "port": { try { this.sourcePort = Integer.parseInt(args[1]); - yield true; + $yield = true; } catch (NumberFormatException e) { - yield false; + $yield = false; } + break; } - case "username" -> { + case "username": { this.sourceUsername = args[1]; - yield true; + $yield = true; + break; } - case "password" -> { + case "password": { this.sourcePassword = args[1]; - yield true; + $yield = true; + break; } - case "database" -> { + case "database": { this.sourceDatabase = args[1]; - yield true; + $yield = true; + break; } - case "players_table" -> { + case "players_table": { this.sourcePlayersTable = args[1]; - yield true; + $yield = true; + break; } - case "data_table" -> { + case "data_table": { this.sourceDataTable = args[1]; - yield true; + $yield = true; + break; } - default -> false; - }) { + default: { + $yield = false; + break; + } + } + if ($yield) { plugin.log(Level.INFO, getHelpMenu()); plugin.log(Level.INFO, "Successfully set " + args[0] + " to " + obfuscateDataString(args[1])); @@ -229,59 +241,65 @@ public class LegacyMigrator extends Migrator { @NotNull @Override public String getHelpMenu() { - return """ - === HuskSync v1.x --> v3.x Migration Wizard ========= - This will migrate all user data from HuskSync v1.x to - HuskSync v3.x's new format. To perform the migration, - please follow the steps below carefully. - - [!] Existing data in the database will be wiped. [!] - - STEP 1] Please ensure no players are on any servers. - - STEP 2] HuskSync will need to connect to the database - used to hold the existing, legacy HuskSync data. - If this is the same database as the one you are - currently using, you probably don't need to change - anything. - Please check that the credentials below are the - correct credentials of the source legacy HuskSync - database. - - host: %source_host% - - port: %source_port% - - username: %source_username% - - password: %source_password% - - database: %source_database% - - players_table: %source_players_table% - - data_table: %source_data_table% - If any of these are not correct, please correct them - using the command: - "husksync migrate legacy set " - (e.g.: "husksync migrate legacy set host 1.2.3.4") - - STEP 3] HuskSync will migrate data into the database - tables configures in the config.yml file of this - server. Please make sure you're happy with this - before proceeding. - - STEP 4] To start the migration, please run: - "husksync migrate legacy start" - """.replaceAll(Pattern.quote("%source_host%"), obfuscateDataString(sourceHost)) - .replaceAll(Pattern.quote("%source_port%"), Integer.toString(sourcePort)) - .replaceAll(Pattern.quote("%source_username%"), obfuscateDataString(sourceUsername)) - .replaceAll(Pattern.quote("%source_password%"), obfuscateDataString(sourcePassword)) - .replaceAll(Pattern.quote("%source_database%"), sourceDatabase) - .replaceAll(Pattern.quote("%source_players_table%"), sourcePlayersTable) - .replaceAll(Pattern.quote("%source_data_table%"), sourceDataTable); + return "=== HuskSync v1.x --> v3.x Migration Wizard =========\n" + + "This will migrate all user data from HuskSync v1.x to\n" + + "HuskSync v3.x's new format. To perform the migration,\n" + + "please follow the steps below carefully.\n" + + "\n" + + "[!] Existing data in the database will be wiped. [!]\n" + + "\n" + + "STEP 1] Please ensure no players are on any servers.\n" + + "\n" + + "STEP 2] HuskSync will need to connect to the database\n" + + "used to hold the existing, legacy HuskSync data.\n" + + "If this is the same database as the one you are\n" + + "currently using, you probably don't need to change\n" + + "anything.\n" + + "Please check that the credentials below are the\n" + + "correct credentials of the source legacy HuskSync\n" + + "database.\n" + + "- host: " + obfuscateDataString(sourceHost) + "\n" + + "- port: " + sourcePort + "\n" + + "- username: " + obfuscateDataString(sourceUsername) + "\n" + + "- password: " + obfuscateDataString(sourcePassword) + "\n" + + "- database: " + sourceDatabase + "\n" + + "- players_table: " + sourcePlayersTable + "\n" + + "- data_table: " + sourceDataTable + "\n" + + "If any of these are not correct, please correct them\n" + + "using the command:\n" + + "\"husksync migrate legacy set \"\n" + + "(e.g.: \"husksync migrate legacy set host 1.2.3.4\")\n" + + "\n" + + "STEP 3] HuskSync will migrate data into the database\n" + + "tables configures in the config.yml file of this\n" + + "server. Please make sure you're happy with this\n" + + "before proceeding.\n" + + "\n" + + "STEP 4] To start the migration, please run:\n" + + "\"husksync migrate legacy start\"\n"; } - private record LegacyData(@NotNull User user, - @NotNull String serializedInventory, @NotNull String serializedEnderChest, - double health, double maxHealth, double healthScale, int hunger, float saturation, - float saturationExhaustion, int selectedSlot, @NotNull String serializedPotionEffects, - int totalExp, int expLevel, float expProgress, - @NotNull String gameMode, @NotNull String serializedStatistics, boolean isFlying, - @NotNull String serializedAdvancements, @NotNull String serializedLocation) { + @Value + private class LegacyData { + @NotNull User user; + @NotNull String serializedInventory; + @NotNull String serializedEnderChest; + double health; + double maxHealth; + double healthScale; + int hunger; + float saturation; + float saturationExhaustion; + int selectedSlot; + @NotNull String serializedPotionEffects; + int totalExp; + int expLevel; + float expProgress; + @NotNull String gameMode; + @NotNull String serializedStatistics; + boolean isFlying; + @NotNull String serializedAdvancements; + @NotNull String serializedLocation; @NotNull public DataSnapshot.Packed toUserData(@NotNull HSLConverter converter, @NotNull HuskSync plugin) { @@ -319,7 +337,7 @@ public class LegacyMigrator extends Migrator { .advancements(BukkitData.Advancements.from(converter .deserializeAdvancementData(serializedAdvancements).stream() .map(data -> Data.Advancements.Advancement.adapt(data.key(), data.criteriaMap())) - .toList())) + .collect(Collectors.toList()))) // Stats .statistics(BukkitData.Statistics.from( diff --git a/bukkit/src/main/java/net/william278/husksync/migrator/MpdbMigrator.java b/bukkit/src/main/java/net/william278/husksync/migrator/MpdbMigrator.java index d32f1d9d..baa2e8ad 100644 --- a/bukkit/src/main/java/net/william278/husksync/migrator/MpdbMigrator.java +++ b/bukkit/src/main/java/net/william278/husksync/migrator/MpdbMigrator.java @@ -21,6 +21,7 @@ package net.william278.husksync.migrator; import com.google.common.collect.Lists; import com.zaxxer.hikari.HikariDataSource; +import lombok.Value; import net.william278.husksync.BukkitHuskSync; import net.william278.husksync.HuskSync; import net.william278.husksync.data.BukkitData; @@ -104,16 +105,14 @@ public class MpdbMigrator extends Migrator { plugin.log(Level.INFO, "Downloading raw data from the MySQLPlayerDataBridge database (this might take a while)..."); final List dataToMigrate = Lists.newArrayList(); try (final Connection connection = connectionPool.getConnection()) { - try (final PreparedStatement statement = connection.prepareStatement(""" - SELECT `%source_inventory_table%`.`player_uuid`, `%source_inventory_table%`.`player_name`, `inventory`, `armor`, `enderchest`, `exp_lvl`, `exp`, `total_exp` - FROM `%source_inventory_table%` - INNER JOIN `%source_ender_chest_table%` - ON `%source_inventory_table%`.`player_uuid` = `%source_ender_chest_table%`.`player_uuid` - INNER JOIN `%source_xp_table%` - ON `%source_inventory_table%`.`player_uuid` = `%source_xp_table%`.`player_uuid`; - """.replaceAll(Pattern.quote("%source_inventory_table%"), sourceInventoryTable) - .replaceAll(Pattern.quote("%source_ender_chest_table%"), sourceEnderChestTable) - .replaceAll(Pattern.quote("%source_xp_table%"), sourceExperienceTable))) { + try (final PreparedStatement statement = connection.prepareStatement( + "SELECT `" + sourceInventoryTable + "`.`player_uuid`, `" + sourceInventoryTable + "`.`player_name`, `inventory`, `armor`, `enderchest`, `exp_lvl`, `exp`, `total_exp` " + + "FROM `" + sourceInventoryTable + "` " + + " INNER JOIN `" + sourceEnderChestTable + "` " + + " ON `" + sourceInventoryTable + "`.`player_uuid` = `" + sourceEnderChestTable + "`.`player_uuid` " + + " INNER JOIN `" + sourceExperienceTable + "` " + + " ON `" + sourceInventoryTable + "`.`player_uuid` = `" + sourceExperienceTable + "`.`player_uuid`;" + )) { try (final ResultSet resultSet = statement.executeQuery()) { int playersMigrated = 0; while (resultSet.next()) { @@ -141,8 +140,8 @@ public class MpdbMigrator extends Migrator { final AtomicInteger playersConverted = new AtomicInteger(); dataToMigrate.forEach(data -> { final DataSnapshot.Packed convertedData = data.toUserData(mpdbConverter, plugin); - plugin.getDatabase().ensureUser(data.user()); - plugin.getDatabase().addSnapshot(data.user(), convertedData); + plugin.getDatabase().ensureUser(data.getUser()); + plugin.getDatabase().addSnapshot(data.getUser(), convertedData); playersConverted.getAndIncrement(); if (playersConverted.get() % 50 == 0) { plugin.log(Level.INFO, "Converted MySQLPlayerDataBridge data for " + playersConverted + " players..."); @@ -160,45 +159,57 @@ public class MpdbMigrator extends Migrator { @Override public void handleConfigurationCommand(@NotNull String[] args) { if (args.length == 2) { - if (switch (args[0].toLowerCase(Locale.ENGLISH)) { - case "host" -> { + boolean $yield; + switch (args[0].toLowerCase(Locale.ENGLISH)) { + case "host": { this.sourceHost = args[1]; - yield true; + $yield = true; + break; } - case "port" -> { + case "port": { try { this.sourcePort = Integer.parseInt(args[1]); - yield true; + $yield = true; } catch (NumberFormatException e) { - yield false; + $yield = false; } + break; } - case "username" -> { + case "username": { this.sourceUsername = args[1]; - yield true; + $yield = true; + break; } - case "password" -> { + case "password": { this.sourcePassword = args[1]; - yield true; + $yield = true; + break; } - case "database" -> { + case "database": { this.sourceDatabase = args[1]; - yield true; + $yield = true; + break; } - case "inventory_table" -> { + case "inventory_table": { this.sourceInventoryTable = args[1]; - yield true; + $yield = true; + break; } - case "ender_chest_table" -> { + case "ender_chest_table": { this.sourceEnderChestTable = args[1]; - yield true; + $yield = true; + break; } - case "experience_table" -> { + case "experience_table": { this.sourceExperienceTable = args[1]; - yield true; + $yield = true; + break; } - default -> false; - }) { + default: { + $yield = false; + } + } + if ($yield) { plugin.log(Level.INFO, getHelpMenu()); plugin.log(Level.INFO, "Successfully set " + args[0] + " to " + obfuscateDataString(args[1])); @@ -226,77 +237,81 @@ public class MpdbMigrator extends Migrator { @NotNull @Override public String getHelpMenu() { - return """ - === MySQLPlayerDataBridge Migration Wizard ========== - NOTE: This migrator currently WORKS WITH MPDB version - v4.9.2 and below! - - This will migrate inventories, ender chests and XP - from the MySQLPlayerDataBridge plugin to HuskSync. - - To prevent excessive migration times, other non-vital - data will not be transferred. - - [!] Existing data in the database will be wiped. [!] - - STEP 1] Please ensure no players are on any servers. - - STEP 2] HuskSync will need to connect to the database - used to hold the source MySQLPlayerDataBridge data. - Please check these database parameters are OK: - - host: %source_host% - - port: %source_port% - - username: %source_username% - - password: %source_password% - - database: %source_database% - - inventory_table: %source_inventory_table% - - ender_chest_table: %source_ender_chest_table% - - experience_table: %source_xp_table% - If any of these are not correct, please correct them - using the command: - "husksync migrate mpdb set " - (e.g.: "husksync migrate set mpdb host 1.2.3.4") - - STEP 3] HuskSync will migrate data into the database - tables configures in the config.yml file of this - server. Please make sure you're happy with this - before proceeding. - - STEP 4] To start the migration, please run: - "husksync migrate start mpdb" - - NOTE: This migrator currently WORKS WITH MPDB version - v4.9.2 and below! - """.replaceAll(Pattern.quote("%source_host%"), obfuscateDataString(sourceHost)) - .replaceAll(Pattern.quote("%source_port%"), Integer.toString(sourcePort)) - .replaceAll(Pattern.quote("%source_username%"), obfuscateDataString(sourceUsername)) - .replaceAll(Pattern.quote("%source_password%"), obfuscateDataString(sourcePassword)) - .replaceAll(Pattern.quote("%source_database%"), sourceDatabase) - .replaceAll(Pattern.quote("%source_inventory_table%"), sourceInventoryTable) - .replaceAll(Pattern.quote("%source_ender_chest_table%"), sourceEnderChestTable) - .replaceAll(Pattern.quote("%source_xp_table%"), sourceExperienceTable); + return "=== MySQLPlayerDataBridge Migration Wizard ==========\n" + + "NOTE: This migrator currently WORKS WITH MPDB version\n" + + "v4.9.2 and below!\n" + + "\n" + + "This will migrate inventories, ender chests and XP\n" + + "from the MySQLPlayerDataBridge plugin to HuskSync.\n" + + "\n" + + "To prevent excessive migration times, other non-vital\n" + + "data will not be transferred.\n" + + "\n" + + "[!] Existing data in the database will be wiped. [!]\n" + + "\n" + + "STEP 1] Please ensure no players are on any servers.\n" + + "\n" + + "STEP 2] HuskSync will need to connect to the database\n" + + "used to hold the source MySQLPlayerDataBridge data.\n" + + "Please check these database parameters are OK:\n" + + "- host: " + obfuscateDataString(sourceHost) + "\n" + + "- port: " + sourcePort + "\n" + + "- username: " + obfuscateDataString(sourceUsername) + "\n" + + "- password: " + obfuscateDataString(sourcePassword) + "\n" + + "- database: " + sourceDatabase + "\n" + + "- inventory_table: " + sourceInventoryTable + "\n" + + "- ender_chest_table: " + sourceEnderChestTable + "\n" + + "- experience_table: " + sourceExperienceTable + "\n" + + "If any of these are not correct, please correct them\n" + + "using the command:\n" + + "\"husksync migrate mpdb set \"\n" + + "(e.g.: \"husksync migrate set mpdb host 1.2.3.4\")\n" + + "\n" + + "STEP 3] HuskSync will migrate data into the database\n" + + "tables configures in the config.yml file of this\n" + + "server. Please make sure you're happy with this\n" + + "before proceeding.\n" + + "\n" + + "STEP 4] To start the migration, please run:\n" + + "\"husksync migrate start mpdb\"\n" + + "\n" + + "NOTE: This migrator currently WORKS WITH MPDB version\n" + + "v4.9.2 and below!\n"; } /** * Represents data exported from the MySQLPlayerDataBridge source database - * - * @param user The user whose data is being migrated - * @param serializedInventory The serialized inventory data - * @param serializedArmor The serialized armor data - * @param serializedEnderChest The serialized ender chest data - * @param expLevel The player's current XP level - * @param expProgress The player's current XP progress - * @param totalExp The player's total XP score */ - private record MpdbData( - @NotNull User user, - @NotNull String serializedInventory, - @NotNull String serializedArmor, - @NotNull String serializedEnderChest, - int expLevel, - float expProgress, - int totalExp - ) { + @Value + private static class MpdbData { + /** + * The user whose data is being migrated + */ + @NotNull User user; + /** + * The serialized inventory data + */ + @NotNull String serializedInventory; + /** + * The serialized armor data + */ + @NotNull String serializedArmor; + /** + * The serialized ender chest data + */ + @NotNull String serializedEnderChest; + /** + * The player's current XP level + */ + int expLevel; + /** + * The player's current XP progress + */ + float expProgress; + /** + * The player's total XP score + */ + int totalExp; /** * Converts exported MySQLPlayerDataBridge data into HuskSync's {@link DataSnapshot} object format diff --git a/bukkit/src/main/java/net/william278/husksync/user/BukkitUser.java b/bukkit/src/main/java/net/william278/husksync/user/BukkitUser.java index 47130b07..f7567d16 100644 --- a/bukkit/src/main/java/net/william278/husksync/user/BukkitUser.java +++ b/bukkit/src/main/java/net/william278/husksync/user/BukkitUser.java @@ -62,7 +62,7 @@ public class BukkitUser extends OnlineUser implements BukkitUserDataHolder { } @Override - @Deprecated(since = "3.6.7") + @Deprecated public void sendToast(@NotNull MineDown title, @NotNull MineDown description, @NotNull String iconMaterial, @NotNull String backgroundType) { plugin.log(Level.WARNING, "Toast notifications are deprecated. " + diff --git a/bukkit/src/main/java/net/william278/husksync/util/BukkitLegacyConverter.java b/bukkit/src/main/java/net/william278/husksync/util/BukkitLegacyConverter.java index 633a344a..53772263 100644 --- a/bukkit/src/main/java/net/william278/husksync/util/BukkitLegacyConverter.java +++ b/bukkit/src/main/java/net/william278/husksync/util/BukkitLegacyConverter.java @@ -77,7 +77,7 @@ public class BukkitLegacyConverter extends LegacyConverter { @NotNull private Map readStatusData(@NotNull JSONObject object) { if (!object.has("status_data")) { - return Map.of(); + return Collections.emptyMap(); } final JSONObject status = object.getJSONObject("status_data"); diff --git a/bukkit/src/main/java/net/william278/husksync/util/BukkitMapPersister.java b/bukkit/src/main/java/net/william278/husksync/util/BukkitMapPersister.java index 0621dbbd..2f99177d 100644 --- a/bukkit/src/main/java/net/william278/husksync/util/BukkitMapPersister.java +++ b/bukkit/src/main/java/net/william278/husksync/util/BukkitMapPersister.java @@ -69,7 +69,7 @@ public interface BukkitMapPersister { if (!getPlugin().getSettings().getSynchronization().isPersistLockedMaps()) { return items; } - return forEachMap(items, map -> this.persistMapView(map, delegateRenderer)); + return private$forEachMap(items, map -> this.persistMapView(map, delegateRenderer)); } /** @@ -83,21 +83,26 @@ public interface BukkitMapPersister { if (!getPlugin().getSettings().getSynchronization().isPersistLockedMaps()) { return items; } - return forEachMap(items, this::applyMapView); + return private$forEachMap(items, this::private$applyMapView); } // Perform an operation on each map in an array of ItemStacks @NotNull - private ItemStack[] forEachMap(ItemStack[] items, @NotNull Function function) { + default ItemStack[] private$forEachMap(ItemStack[] items, @NotNull Function function) { for (int i = 0; i < items.length; i++) { final ItemStack item = items[i]; if (item == null) { continue; } - if (item.getType() == Material.FILLED_MAP && item.hasItemMeta()) { + if (MaterialUtil.isFilledMap(item.getType()) && item.hasItemMeta()) { items[i] = function.apply(item); - } else if (item.getItemMeta() instanceof BlockStateMeta b && b.getBlockState() instanceof ShulkerBox box) { - forEachMap(box.getInventory().getContents(), function); + } else if (item.getItemMeta() instanceof BlockStateMeta && + ((BlockStateMeta) item.getItemMeta()).getBlockState() instanceof ShulkerBox + ) { + BlockStateMeta b = (BlockStateMeta) item.getItemMeta(); + ShulkerBox box = (ShulkerBox) b.getBlockState(); + + private$forEachMap(box.getInventory().getContents(), function); b.setBlockState(box); } } @@ -105,7 +110,7 @@ public interface BukkitMapPersister { } @NotNull - private ItemStack persistMapView(@NotNull ItemStack map, @NotNull Player delegateRenderer) { + default ItemStack persistMapView(@NotNull ItemStack map, @NotNull Player delegateRenderer) { final MapMeta meta = Objects.requireNonNull((MapMeta) map.getItemMeta()); if (!meta.hasMapView()) { return map; @@ -139,7 +144,7 @@ public interface BukkitMapPersister { } @NotNull - private ItemStack applyMapView(@NotNull ItemStack map) { + default ItemStack private$applyMapView(@NotNull ItemStack map) { final MapMeta meta = Objects.requireNonNull((MapMeta) map.getItemMeta()); NBT.get(map, nbt -> { if (!nbt.hasTag(MAP_DATA_KEY)) { @@ -186,8 +191,8 @@ public interface BukkitMapPersister { } // Add a renderer to the map with the data and save to file - final MapView view = generateRenderedMap(canvasData); - final String worldUid = getDefaultMapWorld().getUID().toString(); + final MapView view = private$generateRenderedMap(canvasData); + final String worldUid = private$getDefaultMapWorld().getUID().toString(); meta.setMapView(view); map.setItemMeta(meta); saveMapToFile(canvasData, view.getId()); @@ -204,7 +209,7 @@ public interface BukkitMapPersister { } default void renderMapFromFile(@NotNull MapView view) { - final File mapFile = new File(getMapCacheFolder(), view.getId() + ".dat"); + final File mapFile = new File(private$getMapCacheFolder(), view.getId() + ".dat"); if (!mapFile.exists()) { return; } @@ -232,7 +237,7 @@ public interface BukkitMapPersister { default void saveMapToFile(@NotNull MapData data, int id) { getPlugin().runAsync(() -> { - final File mapFile = new File(getMapCacheFolder(), id + ".dat"); + final File mapFile = new File(private$getMapCacheFolder(), id + ".dat"); if (mapFile.exists()) { return; } @@ -248,7 +253,7 @@ public interface BukkitMapPersister { } @NotNull - private File getMapCacheFolder() { + default File private$getMapCacheFolder() { final File mapCache = new File(getPlugin().getDataFolder(), "maps"); if (!mapCache.exists() && !mapCache.mkdirs()) { getPlugin().log(Level.WARNING, "Failed to create maps folder"); @@ -258,8 +263,8 @@ public interface BukkitMapPersister { // Sets the renderer of a map, and returns the generated MapView @NotNull - private MapView generateRenderedMap(@NotNull MapData canvasData) { - final MapView view = Bukkit.createMap(getDefaultMapWorld()); + default MapView private$generateRenderedMap(@NotNull MapData canvasData) { + final MapView view = Bukkit.createMap(private$getDefaultMapWorld()); view.getRenderers().clear(); // Create a new map view renderer with the map data color at each pixel @@ -275,7 +280,7 @@ public interface BukkitMapPersister { } @NotNull - private static World getDefaultMapWorld() { + static World private$getDefaultMapWorld() { final World world = Bukkit.getWorlds().get(0); if (world == null) { throw new IllegalStateException("No worlds are loaded on the server!"); @@ -318,35 +323,86 @@ public interface BukkitMapPersister { cursors.removeCursor(cursors.getCursor(0)); } - canvasData.getBanners().forEach(banner -> cursors.addCursor(createBannerCursor(banner))); + canvasData.getBanners().forEach(banner -> cursors.addCursor(private$createBannerCursor(banner))); canvas.setCursors(cursors); } } @NotNull - private static MapCursor createBannerCursor(@NotNull MapBanner banner) { + static MapCursor private$createBannerCursor(@NotNull MapBanner banner) { + MapCursor.Type type; + switch (banner.getColor().toLowerCase(Locale.ENGLISH)) { + case "white": { + type = MapCursor.Type.BANNER_WHITE; + break; + } + case "orange": { + type = MapCursor.Type.BANNER_ORANGE; + break; + } + case "magenta": { + type = MapCursor.Type.BANNER_MAGENTA; + break; + } + case "light_blue": { + type = MapCursor.Type.BANNER_LIGHT_BLUE; + break; + } + case "yellow": { + type = MapCursor.Type.BANNER_YELLOW; + break; + } + case "lime": { + type = MapCursor.Type.BANNER_LIME; + break; + } + case "pink": { + type = MapCursor.Type.BANNER_PINK; + break; + } + case "gray": { + type = MapCursor.Type.BANNER_GRAY; + break; + } + case "light_gray": { + type = MapCursor.Type.BANNER_LIGHT_GRAY; + break; + } + case "cyan": { + type = MapCursor.Type.BANNER_CYAN; + break; + } + case "purple": { + type = MapCursor.Type.BANNER_PURPLE; + break; + } + case "blue": { + type = MapCursor.Type.BANNER_BLUE; + break; + } + case "brown": { + type = MapCursor.Type.BANNER_BROWN; + break; + } + case "green": { + type = MapCursor.Type.BANNER_GREEN; + break; + } + case "red": { + type = MapCursor.Type.BANNER_RED; + break; + } + default: { + type = MapCursor.Type.BANNER_BLACK; + break; + } + } + return new MapCursor( (byte) banner.getPosition().getX(), (byte) banner.getPosition().getZ(), (byte) 8, // Always rotate banners upright - switch (banner.getColor().toLowerCase(Locale.ENGLISH)) { - case "white" -> MapCursor.Type.BANNER_WHITE; - case "orange" -> MapCursor.Type.BANNER_ORANGE; - case "magenta" -> MapCursor.Type.BANNER_MAGENTA; - case "light_blue" -> MapCursor.Type.BANNER_LIGHT_BLUE; - case "yellow" -> MapCursor.Type.BANNER_YELLOW; - case "lime" -> MapCursor.Type.BANNER_LIME; - case "pink" -> MapCursor.Type.BANNER_PINK; - case "gray" -> MapCursor.Type.BANNER_GRAY; - case "light_gray" -> MapCursor.Type.BANNER_LIGHT_GRAY; - case "cyan" -> MapCursor.Type.BANNER_CYAN; - case "purple" -> MapCursor.Type.BANNER_PURPLE; - case "blue" -> MapCursor.Type.BANNER_BLUE; - case "brown" -> MapCursor.Type.BANNER_BROWN; - case "green" -> MapCursor.Type.BANNER_GREEN; - case "red" -> MapCursor.Type.BANNER_RED; - default -> MapCursor.Type.BANNER_BLACK; - }, + type, true, banner.getText().isEmpty() ? null : banner.getText() ); @@ -409,11 +465,14 @@ public interface BukkitMapPersister { @NotNull private String getDimension() { - return mapView.getWorld() != null ? switch (mapView.getWorld().getEnvironment()) { - case NETHER -> "minecraft:the_nether"; - case THE_END -> "minecraft:the_end"; - default -> "minecraft:overworld"; - } : "minecraft:overworld"; + if (mapView.getWorld() == null) { + return "minecraft:overworld"; + } + switch (mapView.getWorld().getEnvironment()) { + case NETHER: return "minecraft:the_nether"; + case THE_END: return "minecraft:the_end"; + default: return "minecraft:overworld"; + } } /** @@ -442,7 +501,7 @@ public interface BukkitMapPersister { } } catch (Throwable ignored) { } - return MapData.fromPixels(pixels, getDimension(), (byte) 2, banners, List.of()); + return MapData.fromPixels(pixels, getDimension(), (byte) 2, banners, Collections.emptyList()); } } diff --git a/bukkit/src/main/java/net/william278/husksync/util/MaterialUtil.java b/bukkit/src/main/java/net/william278/husksync/util/MaterialUtil.java new file mode 100644 index 00000000..b6f52895 --- /dev/null +++ b/bukkit/src/main/java/net/william278/husksync/util/MaterialUtil.java @@ -0,0 +1,58 @@ +/* + * This file is part of HuskSync, licensed under the Apache License 2.0. + * + * Copyright (c) William278 + * Copyright (c) contributors + * + * 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 net.william278.husksync.util; + +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import net.william278.husksync.util.ref.RefUtil; +import org.bukkit.Material; + +import java.lang.invoke.MethodHandle; + +@UtilityClass +public class MaterialUtil { + private static final MethodHandle v$isAirMethod = RefUtil.bootstrapVirtualMethod("Lorg/bukkit/Material;isAir()Z"); + + @SneakyThrows + public static boolean isAir(Material $this) { + if (v$isAirMethod != null) { + return (boolean) v$isAirMethod.invoke($this); + } + switch ($this.name()) { + case "AIR": + case "CAVE_AIR": + case "VOID_AIR": + case "LEGACY_AIR": + return true; + default: + return false; + } + } + + public static boolean isFilledMap(Material $this) { + switch ($this.name()) { + case "FILLED_MAP": + case "LEGACY_MAP": + return true; + default: + return false; + } + } +} diff --git a/bukkit/src/main/java/net/william278/husksync/util/ref/RefUtil.java b/bukkit/src/main/java/net/william278/husksync/util/ref/RefUtil.java new file mode 100644 index 00000000..97765653 --- /dev/null +++ b/bukkit/src/main/java/net/william278/husksync/util/ref/RefUtil.java @@ -0,0 +1,220 @@ +/* + * This file is part of HuskSync, licensed under the Apache License 2.0. + * + * Copyright (c) William278 + * Copyright (c) contributors + * + * 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 net.william278.husksync.util.ref; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Arrays; + +public final class RefUtil { + private RefUtil() { + throw new UnsupportedOperationException("This class cannot be instantiated"); + } + + private static final MethodHandles.Lookup lookup = MethodHandles.lookup(); + + public static MethodHandle bootstrapStaticMethod(String methodDesc) { + String[] descParts = splitMethodDesc(methodDesc); + Class owner; + try { + owner = Class.forName(descParts[0].replace('/', '.')); + } catch (ClassNotFoundException e) { + return null; + } + MethodType methodType; + try { + methodType = MethodType.fromMethodDescriptorString(descParts[2], owner.getClassLoader()); + } catch (TypeNotPresentException e) { + return null; + } + try { + return lookup.findStatic(owner, descParts[1], methodType); + } catch (NoSuchMethodException e) { + return null; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + public static MethodHandle bootstrapVirtualMethod(String methodDesc) { + String[] descParts = splitMethodDesc(methodDesc); + Class owner; + try { + owner = Class.forName(descParts[0].replace('/', '.')); + } catch (ClassNotFoundException e) { + return null; + } + MethodType methodType; + try { + methodType = MethodType.fromMethodDescriptorString(descParts[2], owner.getClassLoader()); + } catch (TypeNotPresentException e) { + return null; + } + try { + return lookup.findVirtual(owner, descParts[1], methodType); + } catch (NoSuchMethodException e) { + return null; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private static String[] splitMethodDesc(String methodDesc) { + if (methodDesc.length() < 5) { // L;()V + throw new IllegalArgumentException("Invalid method descriptor: " + methodDesc); + } + + int descSplit = methodDesc.indexOf(';'); + if (descSplit == -1) { + throw new IllegalArgumentException("Invalid method descriptor: " + methodDesc); + } + + int argsStart = methodDesc.indexOf('('); + if (argsStart == -1) { + throw new IllegalArgumentException("Invalid method descriptor: " + methodDesc); + } + + return new String[] { + methodDesc.substring(1, descSplit), // java/lang/Object + methodDesc.substring(descSplit + 1, argsStart), // method name + methodDesc.substring(argsStart) // (Ljava/lang/Object;)V + }; + } + + public static RuntimeException sneakyThrow(Throwable t) throws T { + throw (T) t; + } + + public static MethodHandle bootstrapStaticFieldGet(String fieldDesc) { + String[] descParts = splitFieldDesc(fieldDesc); + Class owner; + try { + owner = Class.forName(descParts[0].replace('/', '.')); + } catch (ClassNotFoundException e) { + return null; + } + Class fieldType; + try { + fieldType = MethodType.fromMethodDescriptorString("()" + descParts[2], owner.getClassLoader()).returnType(); + } catch (TypeNotPresentException e) { + return null; + } + try { + return lookup.findStaticGetter(owner, descParts[1], fieldType); + } catch (NoSuchFieldException e) { + return null; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + public static MethodHandle bootstrapStaticFieldSet(String fieldDesc) { + String[] descParts = splitFieldDesc(fieldDesc); + Class owner; + try { + owner = Class.forName(descParts[0].replace('/', '.')); + } catch (ClassNotFoundException e) { + return null; + } + Class fieldType; + try { + fieldType = MethodType.fromMethodDescriptorString("()" + descParts[2], owner.getClassLoader()).returnType(); + } catch (TypeNotPresentException e) { + return null; + } + try { + return lookup.findStaticSetter(owner, descParts[1], fieldType); + } catch (NoSuchFieldException e) { + return null; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + public static MethodHandle bootstrapVirtualFieldGet(String fieldDesc) { + String[] descParts = splitFieldDesc(fieldDesc); + Class owner; + try { + owner = Class.forName(descParts[0].replace('/', '.')); + } catch (ClassNotFoundException e) { + return null; + } + Class fieldType; + try { + fieldType = MethodType.fromMethodDescriptorString("()" + descParts[2], owner.getClassLoader()).returnType(); + } catch (TypeNotPresentException e) { + return null; + } + try { + return lookup.findGetter(owner, descParts[1], fieldType); + } catch (NoSuchFieldException e) { + return null; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + public static MethodHandle bootstrapVirtualFieldSet(String fieldDesc) { + String[] descParts = splitFieldDesc(fieldDesc); + Class owner; + try { + owner = Class.forName(descParts[0].replace('/', '.')); + } catch (ClassNotFoundException e) { + return null; + } + Class fieldType; + try { + fieldType = MethodType.fromMethodDescriptorString("()" + descParts[2], owner.getClassLoader()).returnType(); + } catch (TypeNotPresentException e) { + return null; + } + try { + return lookup.findSetter(owner, descParts[1], fieldType); + } catch (NoSuchFieldException e) { + return null; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private static String[] splitFieldDesc(String fieldDesc) { + if (fieldDesc.length() < 3) { // L;: + throw new IllegalArgumentException("Invalid field descriptor: " + fieldDesc); + } + + int descSplit = fieldDesc.indexOf(';'); + if (descSplit == -1) { + throw new IllegalArgumentException("Invalid field descriptor: " + fieldDesc); + } + + int argsStart = fieldDesc.indexOf(':'); + if (argsStart == -1) { + throw new IllegalArgumentException("Invalid field descriptor: " + fieldDesc); + } + + return new String[] { + fieldDesc.substring(1, descSplit), // java/lang/Object + fieldDesc.substring(descSplit + 1, argsStart), // field name + fieldDesc.substring(argsStart + 1) // Ljava/lang/Object; + }; + } + +} diff --git a/bukkit/src/main/resources/plugin.yml b/bukkit/src/main/resources/plugin.yml index 71aa4c2d..34ac5027 100644 --- a/bukkit/src/main/resources/plugin.yml +++ b/bukkit/src/main/resources/plugin.yml @@ -1,20 +1,13 @@ name: 'HuskSync' version: '${version}' main: 'net.william278.husksync.BukkitHuskSync' -api-version: 1.17 -author: 'William278' +api-version: 1.13 +author: 'William278, InkerBot' description: '${description}' -website: 'https://william278.net' +website: 'https://git.inker.bot/HuskLegacy/HuskSync' folia-supported: true softdepend: - 'packetevents' - 'ProtocolLib' - 'MysqlPlayerDataBridge' - 'Plan' -libraries: - - 'redis.clients:jedis:${jedis_version}' - - 'com.mysql:mysql-connector-j:${mysql_driver_version}' - - 'org.mariadb.jdbc:mariadb-java-client:${mariadb_driver_version}' - - 'org.postgresql:postgresql:${postgres_driver_version}' - - 'org.mongodb:mongodb-driver-sync:${mongodb_driver_version}' - - 'org.xerial.snappy:snappy-java:${snappy_version}' \ No newline at end of file diff --git a/common/build.gradle b/common/build.gradle index a9186070..39d400d2 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -3,21 +3,21 @@ plugins { } dependencies { + api 'org.inksnow.cputil:logger:1.13' + api 'org.inksnow.cputil:database:1.13' + api 'commons-io:commons-io:2.16.1' api 'org.apache.commons:commons-text:1.12.0' - api 'net.william278:minedown:1.8.2' + api 'de.themoep:minedown-adventure:1.7.3-SNAPSHOT' api 'org.json:json:20240303' api 'com.google.code.gson:gson:2.11.0' api 'com.fatboyindustrial.gson-javatime-serialisers:gson-javatime-serialisers:1.1.2' - api 'de.exlll:configlib-yaml:4.5.0' - api 'net.william278:paginedown:1.1.2' - api 'net.william278:DesertWell:2.0.4' - api('com.zaxxer:HikariCP:5.1.0') { - exclude module: 'slf4j-api' - } + api 'org.inksnow.husk:configlib-yaml:4.5.4' + api 'org.inksnow.husk:paginedown:1.1.3-ac18d11' + api 'org.inksnow.husk:desertwell:2.0.5-9962d59:all' + api 'com.mojang:brigadier:1.1.8' - compileOnly 'net.william278.uniform:uniform-common:1.2.1' - compileOnly 'com.mojang:brigadier:1.1.8' + compileOnly 'org.inksnow.husk.uniform:uniform-common:1.2.1-ad25f16' compileOnly 'org.projectlombok:lombok:1.18.34' compileOnly 'org.jetbrains:annotations:24.1.0' compileOnly 'net.kyori:adventure-api:4.17.0' @@ -35,7 +35,7 @@ dependencies { testImplementation "org.xerial.snappy:snappy-java:$snappy_version" testImplementation 'com.google.guava:guava:33.2.1-jre' testImplementation 'com.github.plan-player-analytics:Plan:5.5.2272' - testCompileOnly 'de.exlll:configlib-yaml:4.5.0' + testCompileOnly 'org.inksnow.husk:configlib-yaml:4.5.4' testCompileOnly 'org.jetbrains:annotations:24.1.0' annotationProcessor 'org.projectlombok:lombok:1.18.34' diff --git a/common/src/main/java/net/william278/husksync/HuskSync.java b/common/src/main/java/net/william278/husksync/HuskSync.java index 962c272a..c79f9d75 100644 --- a/common/src/main/java/net/william278/husksync/HuskSync.java +++ b/common/src/main/java/net/william278/husksync/HuskSync.java @@ -196,7 +196,7 @@ public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider // Get the debug log message format @NotNull - private String getDebugString(@NotNull String message) { + default String getDebugString(@NotNull String message) { return String.format("[DEBUG] [%s] %s", new SimpleDateFormat("mm:ss.SSS").format(new Date()), message); } @@ -327,16 +327,16 @@ public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider */ final class FailedToLoadException extends IllegalStateException { - private static final String FORMAT = """ - HuskSync has failed to load! The plugin will not be enabled and no data will be synchronized. - Please make sure the plugin has been setup correctly (https://william278.net/docs/husksync/setup): - - 1) Make sure you've entered your MySQL, MariaDB or MongoDB database details correctly in config.yml - 2) Make sure your Redis server details are also correct in config.yml - 3) Make sure your config is up-to-date (https://william278.net/docs/husksync/config-file) - 4) Check the error below for more details - - Caused by: %s"""; + private static final String FORMAT = + "HuskSync has failed to load! The plugin will not be enabled and no data will be synchronized.\n" + + "Please make sure the plugin has been setup correctly (https://william278.net/docs/husksync/setup):\n" + + "\n" + + "1) Make sure you've entered your MySQL, MariaDB or MongoDB database details correctly in config.yml\n" + + "2) Make sure your Redis server details are also correct in config.yml\n" + + "3) Make sure your config is up-to-date (https://william278.net/docs/husksync/config-file)\n" + + "4) Check the error below for more details\n" + + "\n" + + "Caused by: %s"; FailedToLoadException(@NotNull String message, @NotNull Throwable cause) { super(String.format(FORMAT, message), cause); diff --git a/common/src/main/java/net/william278/husksync/api/HuskSyncAPI.java b/common/src/main/java/net/william278/husksync/api/HuskSyncAPI.java index 451d9b90..4d40d79b 100644 --- a/common/src/main/java/net/william278/husksync/api/HuskSyncAPI.java +++ b/common/src/main/java/net/william278/husksync/api/HuskSyncAPI.java @@ -29,15 +29,19 @@ import net.william278.husksync.data.Serializer; import net.william278.husksync.sync.DataSyncer; import net.william278.husksync.user.OnlineUser; import net.william278.husksync.user.User; +import net.william278.husksync.util.OptionalUtil; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Formatter; import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * The common implementation of the HuskSync API, containing cross-platform API calls. @@ -138,7 +142,7 @@ public class HuskSyncAPI { public CompletableFuture> getCurrentData(@NotNull User user) { return plugin.getRedisManager() .getUserData(UUID.randomUUID(), user) - .thenApply(data -> data.or(() -> plugin.getDatabase().getLatestSnapshot(user))) + .thenApply(data -> OptionalUtil.or(data, () -> plugin.getDatabase().getLatestSnapshot(user))) .thenApply(data -> data.map(snapshot -> snapshot.unpack(plugin))); } @@ -154,8 +158,8 @@ public class HuskSyncAPI { */ public void setCurrentData(@NotNull User user, @NotNull DataSnapshot data) { plugin.runAsync(() -> { - final DataSnapshot.Packed packed = data instanceof DataSnapshot.Unpacked unpacked - ? unpacked.pack(plugin) : (DataSnapshot.Packed) data; + final DataSnapshot.Packed packed = data instanceof DataSnapshot.Unpacked + ? ((DataSnapshot.Unpacked) data).pack(plugin) : (DataSnapshot.Packed) data; addSnapshot(user, packed); plugin.getRedisManager().sendUserDataUpdate(user, packed); }); @@ -190,7 +194,7 @@ public class HuskSyncAPI { return plugin.supplyAsync( () -> plugin.getDatabase().getAllSnapshots(user).stream() .map(snapshot -> snapshot.unpack(plugin)) - .toList() + .collect(Collectors.toList()) ); } @@ -205,9 +209,10 @@ public class HuskSyncAPI { */ public CompletableFuture> getSnapshot(@NotNull User user, @NotNull UUID versionId) { return plugin.supplyAsync( - () -> plugin.getDatabase().getSnapshot(user, versionId).stream() + () -> plugin.getDatabase().getSnapshot(user, versionId) + .map(Stream::of).orElseGet(Stream::empty) .map(snapshot -> snapshot.unpack(plugin)) - .toList() + .collect(Collectors.toList()) ); } @@ -274,8 +279,8 @@ public class HuskSyncAPI { @Nullable BiConsumer callback) { plugin.runAsync(() -> plugin.getDataSyncer().saveData( user, - snapshot instanceof DataSnapshot.Unpacked unpacked - ? unpacked.pack(plugin) : (DataSnapshot.Packed) snapshot, + snapshot instanceof DataSnapshot.Unpacked + ? ((DataSnapshot.Unpacked) snapshot).pack(plugin) : (DataSnapshot.Packed) snapshot, callback )); } @@ -304,8 +309,8 @@ public class HuskSyncAPI { */ public void updateSnapshot(@NotNull User user, @NotNull DataSnapshot snapshot) { plugin.runAsync(() -> plugin.getDatabase().updateSnapshot( - user, snapshot instanceof DataSnapshot.Unpacked unpacked - ? unpacked.pack(plugin) : (DataSnapshot.Packed) snapshot + user, snapshot instanceof DataSnapshot.Unpacked + ? ((DataSnapshot.Unpacked) snapshot).pack(plugin) : (DataSnapshot.Packed) snapshot )); } @@ -439,8 +444,8 @@ public class HuskSyncAPI { * @since 3.0 */ public int getSnapshotFileSize(@NotNull DataSnapshot snapshot) { - return (snapshot instanceof DataSnapshot.Packed packed) - ? packed.getFileSize(plugin) + return (snapshot instanceof DataSnapshot.Packed) + ? ((DataSnapshot.Packed) snapshot).getFileSize(plugin) : ((DataSnapshot.Unpacked) snapshot).pack(plugin).getFileSize(plugin); } @@ -511,15 +516,14 @@ public class HuskSyncAPI { */ static final class NotRegisteredException extends IllegalStateException { - private static final String REASONS = """ - This may be because: - 1) HuskSync has failed to enable successfully - 2) Your plugin isn't set to load after HuskSync has - (Check if it set as a (soft)depend in plugin.yml or to load: BEFORE in paper-plugin.yml?) - 3) You are attempting to access HuskSync on plugin construction/before your plugin has enabled."""; + private static final String REASONS = "This may be because:\n" + + "1) HuskSync has failed to enable successfully\n" + + "2) Your plugin isn't set to load after HuskSync has\n" + + " (Check if it set as a (soft)depend in plugin.yml or to load: BEFORE in paper-plugin.yml?)\n" + + "3) You are attempting to access HuskSync on plugin construction/before your plugin has enabled."; NotRegisteredException(@NotNull String reasons) { - super("Could not access the HuskSync API as it has not yet been registered. %s".formatted(reasons)); + super("Could not access the HuskSync API as it has not yet been registered. " + reasons); } NotRegisteredException() { diff --git a/common/src/main/java/net/william278/husksync/command/EnderChestCommand.java b/common/src/main/java/net/william278/husksync/command/EnderChestCommand.java index 81395e9b..157c0a4b 100644 --- a/common/src/main/java/net/william278/husksync/command/EnderChestCommand.java +++ b/common/src/main/java/net/william278/husksync/command/EnderChestCommand.java @@ -19,6 +19,7 @@ package net.william278.husksync.command; +import com.google.common.collect.ImmutableList; import de.themoep.minedown.adventure.MineDown; import net.william278.husksync.HuskSync; import net.william278.husksync.data.Data; @@ -37,14 +38,14 @@ import java.util.Optional; public class EnderChestCommand extends ItemsCommand { public EnderChestCommand(@NotNull HuskSync plugin) { - super("enderchest", List.of("echest", "openechest"), plugin); + super("enderchest", ImmutableList.of("echest", "openechest"), plugin); } @Override protected void showItems(@NotNull OnlineUser viewer, @NotNull DataSnapshot.Unpacked snapshot, @NotNull User user, boolean allowEdit) { final Optional optionalEnderChest = snapshot.getEnderChest(); - if (optionalEnderChest.isEmpty()) { + if (!optionalEnderChest.isPresent()) { plugin.getLocales().getLocale("error_no_data_to_display") .ifPresent(viewer::sendMessage); return; @@ -76,7 +77,7 @@ public class EnderChestCommand extends ItemsCommand { @SuppressWarnings("DuplicatedCode") private void updateItems(@NotNull OnlineUser viewer, @NotNull Data.Items.Items items, @NotNull User holder) { final Optional latestData = plugin.getDatabase().getLatestSnapshot(holder); - if (latestData.isEmpty()) { + if (!latestData.isPresent()) { plugin.getLocales().getLocale("error_no_data_to_display") .ifPresent(viewer::sendMessage); return; diff --git a/common/src/main/java/net/william278/husksync/command/HuskSyncCommand.java b/common/src/main/java/net/william278/husksync/command/HuskSyncCommand.java index 80d38823..b1de1014 100644 --- a/common/src/main/java/net/william278/husksync/command/HuskSyncCommand.java +++ b/common/src/main/java/net/william278/husksync/command/HuskSyncCommand.java @@ -19,6 +19,7 @@ package net.william278.husksync.command; +import com.google.common.collect.ImmutableList; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import de.themoep.minedown.adventure.MineDown; @@ -30,9 +31,11 @@ import net.kyori.adventure.text.format.TextColor; import net.william278.desertwell.about.AboutMenu; import net.william278.desertwell.util.UpdateChecker; import net.william278.husksync.HuskSync; +import net.william278.husksync.data.Identifier; import net.william278.husksync.database.Database; import net.william278.husksync.migrator.Migrator; import net.william278.husksync.user.CommandUser; +import net.william278.husksync.util.StringUtil; import net.william278.uniform.BaseCommand; import net.william278.uniform.CommandProvider; import net.william278.uniform.Permission; @@ -41,7 +44,7 @@ import org.apache.commons.text.WordUtils; import org.jetbrains.annotations.NotNull; import java.util.Arrays; -import java.util.List; +import java.util.Set; import java.util.function.Function; import java.util.logging.Level; import java.util.stream.Collectors; @@ -52,7 +55,7 @@ public class HuskSyncCommand extends PluginCommand { private final AboutMenu aboutMenu; public HuskSyncCommand(@NotNull HuskSync plugin) { - super("husksync", List.of(), Permission.Default.TRUE, ExecutionScope.ALL, plugin); + super("husksync", ImmutableList.of(), Permission.Default.TRUE, ExecutionScope.ALL, plugin); this.updateChecker = plugin.getUpdateChecker(); this.aboutMenu = AboutMenu.builder() .title(Component.text("HuskSync")) @@ -110,7 +113,8 @@ public class HuskSyncCommand extends PluginCommand { plugin.getLocales().getLocale("system_status_header").ifPresent(user::sendMessage); user.sendMessage(Component.join( JoinConfiguration.newlines(), - Arrays.stream(StatusLine.values()).map(s -> s.get(plugin)).toList() + Arrays.stream(StatusLine.values()).map(s -> s.get(plugin)) + .collect(Collectors.toList()) )); }); } @@ -202,7 +206,7 @@ public class HuskSyncCommand extends PluginCommand { private enum StatusLine { PLUGIN_VERSION(plugin -> Component.text("v" + plugin.getPluginVersion().toStringWithoutMetadata()) - .appendSpace().append(plugin.getPluginVersion().getMetadata().isBlank() ? Component.empty() + .appendSpace().append(StringUtil.isBlank(plugin.getPluginVersion().getMetadata()) ? Component.empty() : Component.text("(build " + plugin.getPluginVersion().getMetadata() + ")"))), SERVER_VERSION(plugin -> Component.text(plugin.getServerVersion())), LANGUAGE(plugin -> Component.text(plugin.getSettings().getLanguage())), @@ -210,7 +214,7 @@ public class HuskSyncCommand extends PluginCommand { JAVA_VERSION(plugin -> Component.text(System.getProperty("java.version"))), JAVA_VENDOR(plugin -> Component.text(System.getProperty("java.vendor"))), SERVER_NAME(plugin -> Component.text(plugin.getServerName())), - CLUSTER_ID(plugin -> Component.text(plugin.getSettings().getClusterId().isBlank() ? "None" : plugin.getSettings().getClusterId())), + CLUSTER_ID(plugin -> Component.text(StringUtil.isBlank(plugin.getSettings().getClusterId()) ? "None" : plugin.getSettings().getClusterId())), SYNC_MODE(plugin -> Component.text(WordUtils.capitalizeFully( plugin.getSettings().getSynchronization().getMode().toString() ))), @@ -224,10 +228,10 @@ public class HuskSyncCommand extends PluginCommand { ), IS_DATABASE_LOCAL(plugin -> getLocalhostBoolean(plugin.getSettings().getDatabase().getCredentials().getHost())), USING_REDIS_SENTINEL(plugin -> getBoolean( - !plugin.getSettings().getRedis().getSentinel().getMaster().isBlank() + !StringUtil.isBlank(plugin.getSettings().getRedis().getSentinel().getMaster()) )), USING_REDIS_PASSWORD(plugin -> getBoolean( - !plugin.getSettings().getRedis().getCredentials().getPassword().isBlank() + !StringUtil.isBlank(plugin.getSettings().getRedis().getCredentials().getPassword()) )), REDIS_USING_SSL(plugin -> getBoolean( plugin.getSettings().getRedis().getCredentials().isUseSsl() @@ -243,13 +247,13 @@ public class HuskSyncCommand extends PluginCommand { .hoverEvent(HoverEvent.showText( Component.text(i.isEnabled() ? "Enabled" : "Disabled") .append(Component.newline()) - .append(Component.text("Dependencies: %s".formatted(i.getDependencies() - .isEmpty() ? "(None)" : i.getDependencies().stream() - .map(d -> "%s (%s)".formatted( - d.getKey().value(), d.isRequired() ? "Required" : "Optional" - )).collect(Collectors.joining(", "))) - ).color(NamedTextColor.GRAY)) - ))).toList() + .append(i.getDependencies().isEmpty() + ? Component.text("Dependencies: None").color(NamedTextColor.GRAY) + : Component.text("Dependencies: " + i.getDependencies().stream() + .map(d -> d.getKey().value() + " (" + (d.isRequired() ? "Required" : "Optional") + ")") + .collect(Collectors.joining(", ")) + ).color(NamedTextColor.GRAY)) + ))).collect(Collectors.toList()) )); private final Function supplier; diff --git a/common/src/main/java/net/william278/husksync/command/InventoryCommand.java b/common/src/main/java/net/william278/husksync/command/InventoryCommand.java index 0eadbb0a..e9fa06c3 100644 --- a/common/src/main/java/net/william278/husksync/command/InventoryCommand.java +++ b/common/src/main/java/net/william278/husksync/command/InventoryCommand.java @@ -19,6 +19,7 @@ package net.william278.husksync.command; +import com.google.common.collect.ImmutableList; import de.themoep.minedown.adventure.MineDown; import net.william278.husksync.HuskSync; import net.william278.husksync.data.Data; @@ -37,14 +38,14 @@ import java.util.Optional; public class InventoryCommand extends ItemsCommand { public InventoryCommand(@NotNull HuskSync plugin) { - super("inventory", List.of("invsee", "openinv"), plugin); + super("inventory", ImmutableList.of("invsee", "openinv"), plugin); } @Override protected void showItems(@NotNull OnlineUser viewer, @NotNull DataSnapshot.Unpacked snapshot, @NotNull User user, boolean allowEdit) { final Optional optionalInventory = snapshot.getInventory(); - if (optionalInventory.isEmpty()) { + if (!optionalInventory.isPresent()) { viewer.sendMessage(new MineDown("what the FUCK is happening")); plugin.getLocales().getLocale("error_no_data_to_display") .ifPresent(viewer::sendMessage); @@ -77,7 +78,7 @@ public class InventoryCommand extends ItemsCommand { @SuppressWarnings("DuplicatedCode") private void updateItems(@NotNull OnlineUser viewer, @NotNull Data.Items.Items items, @NotNull User holder) { final Optional latestData = plugin.getDatabase().getLatestSnapshot(holder); - if (latestData.isEmpty()) { + if (!latestData.isPresent()) { plugin.getLocales().getLocale("error_no_data_to_display") .ifPresent(viewer::sendMessage); return; diff --git a/common/src/main/java/net/william278/husksync/command/ItemsCommand.java b/common/src/main/java/net/william278/husksync/command/ItemsCommand.java index ecdee745..6ed10343 100644 --- a/common/src/main/java/net/william278/husksync/command/ItemsCommand.java +++ b/common/src/main/java/net/william278/husksync/command/ItemsCommand.java @@ -24,6 +24,7 @@ import net.william278.husksync.data.DataSnapshot; import net.william278.husksync.user.CommandUser; import net.william278.husksync.user.OnlineUser; import net.william278.husksync.user.User; +import net.william278.husksync.util.OptionalUtil; import net.william278.uniform.BaseCommand; import net.william278.uniform.Permission; import org.jetbrains.annotations.NotNull; @@ -44,32 +45,32 @@ public abstract class ItemsCommand extends PluginCommand { final User user = ctx.getArgument("username", User.class); final UUID version = ctx.getArgument("version", UUID.class); final CommandUser executor = user(command, ctx); - if (!(executor instanceof OnlineUser online)) { + if (!(executor instanceof OnlineUser)) { plugin.getLocales().getLocale("error_in_game_command_only") .ifPresent(executor::sendMessage); return; } - this.showSnapshotItems(online, user, version); + this.showSnapshotItems((OnlineUser) executor, user, version); }, user("username"), uuid("version")); command.addSyntax((ctx) -> { final User user = ctx.getArgument("username", User.class); final CommandUser executor = user(command, ctx); - if (!(executor instanceof OnlineUser online)) { + if (!(executor instanceof OnlineUser)) { plugin.getLocales().getLocale("error_in_game_command_only") .ifPresent(executor::sendMessage); return; } - this.showLatestItems(online, user); + this.showLatestItems((OnlineUser) executor, user); }, user("username")); } // View (and edit) the latest user data private void showLatestItems(@NotNull OnlineUser viewer, @NotNull User user) { - plugin.getRedisManager().getUserData(user.getUuid(), user).thenAccept(data -> data - .or(() -> plugin.getDatabase().getLatestSnapshot(user)) - .or(() -> { - plugin.getLocales().getLocale("error_no_data_to_display") - .ifPresent(viewer::sendMessage); + plugin.getRedisManager().getUserData(user.getUuid(), user).thenAccept(data -> OptionalUtil.or( + OptionalUtil.or(data, () -> + plugin.getDatabase().getLatestSnapshot(user)), + () -> { + plugin.getLocales().getLocale("error_no_data_to_display").ifPresent(viewer::sendMessage); return Optional.empty(); }) .flatMap(packed -> { @@ -87,8 +88,8 @@ public abstract class ItemsCommand extends PluginCommand { // View a specific version of the user data private void showSnapshotItems(@NotNull OnlineUser viewer, @NotNull User user, @NotNull UUID version) { - plugin.getDatabase().getSnapshot(user, version) - .or(() -> { + OptionalUtil.or(plugin.getDatabase().getSnapshot(user, version), + () -> { plugin.getLocales().getLocale("error_invalid_version_uuid") .ifPresent(viewer::sendMessage); return Optional.empty(); diff --git a/common/src/main/java/net/william278/husksync/command/PluginCommand.java b/common/src/main/java/net/william278/husksync/command/PluginCommand.java index b1eda665..d0bc247e 100644 --- a/common/src/main/java/net/william278/husksync/command/PluginCommand.java +++ b/common/src/main/java/net/william278/husksync/command/PluginCommand.java @@ -46,7 +46,7 @@ public abstract class PluginCommand extends Command { } private static String getDescription(@NotNull HuskSync plugin, @NotNull String name) { - return plugin.getLocales().getRawLocale("%s_command_description".formatted(name)).orElse(""); + return plugin.getLocales().getRawLocale(name + "_command_description").orElse(""); } @NotNull @@ -72,7 +72,10 @@ public abstract class PluginCommand extends Command { @NotNull protected CommandUser adapt(net.william278.uniform.CommandUser user) { - return user.getUuid() == null ? plugin.getConsole() : plugin.getOnlineUser(user.getUuid()).orElseThrow(); + return user.getUuid() == null + ? plugin.getConsole() + : plugin.getOnlineUser(user.getUuid()) + .orElseThrow(() -> new IllegalStateException("Online not found")); } @NotNull diff --git a/common/src/main/java/net/william278/husksync/command/UserDataCommand.java b/common/src/main/java/net/william278/husksync/command/UserDataCommand.java index 08f25bfd..0d0f4443 100644 --- a/common/src/main/java/net/william278/husksync/command/UserDataCommand.java +++ b/common/src/main/java/net/william278/husksync/command/UserDataCommand.java @@ -29,12 +29,14 @@ import net.william278.husksync.user.User; import net.william278.husksync.util.DataDumper; import net.william278.husksync.util.DataSnapshotList; import net.william278.husksync.util.DataSnapshotOverview; +import net.william278.husksync.util.OptionalUtil; import net.william278.uniform.BaseCommand; import net.william278.uniform.CommandProvider; import net.william278.uniform.Permission; import net.william278.uniform.element.ArgumentElement; import org.jetbrains.annotations.NotNull; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Optional; @@ -44,7 +46,7 @@ import java.util.logging.Level; public class UserDataCommand extends PluginCommand { public UserDataCommand(@NotNull HuskSync plugin) { - super("userdata", List.of("playerdata"), Permission.Default.IF_OP, ExecutionScope.ALL, plugin); + super("userdata", Collections.singletonList("playerdata"), Permission.Default.IF_OP, ExecutionScope.ALL, plugin); } @Override @@ -59,35 +61,37 @@ public class UserDataCommand extends PluginCommand { // Show the latest snapshot private void viewLatestSnapshot(@NotNull CommandUser executor, @NotNull User user) { - plugin.getDatabase().getLatestSnapshot(user).ifPresentOrElse( - data -> { - if (data.isInvalid()) { - plugin.getLocales().getLocale("error_invalid_data", data.getInvalidReason(plugin)) - .ifPresent(executor::sendMessage); - return; - } - DataSnapshotOverview.of(data.unpack(plugin), data.getFileSize(plugin), user, plugin) - .show(executor); - }, - () -> plugin.getLocales().getLocale("error_no_data_to_display") - .ifPresent(executor::sendMessage) + OptionalUtil.ifPresentOrElse( + plugin.getDatabase().getLatestSnapshot(user), + data -> { + if (data.isInvalid()) { + plugin.getLocales().getLocale("error_invalid_data", data.getInvalidReason(plugin)) + .ifPresent(executor::sendMessage); + return; + } + DataSnapshotOverview.of(data.unpack(plugin), data.getFileSize(plugin), user, plugin) + .show(executor); + }, + () -> plugin.getLocales().getLocale("error_no_data_to_display") + .ifPresent(executor::sendMessage) ); } // Show the specified snapshot private void viewSnapshot(@NotNull CommandUser executor, @NotNull User user, @NotNull UUID version) { - plugin.getDatabase().getSnapshot(user, version).ifPresentOrElse( - data -> { - if (data.isInvalid()) { - plugin.getLocales().getLocale("error_invalid_data", data.getInvalidReason(plugin)) - .ifPresent(executor::sendMessage); - return; - } - DataSnapshotOverview.of(data.unpack(plugin), data.getFileSize(plugin), user, plugin) - .show(executor); - }, - () -> plugin.getLocales().getLocale("error_invalid_version_uuid") - .ifPresent(executor::sendMessage) + OptionalUtil.ifPresentOrElse( + plugin.getDatabase().getSnapshot(user, version), + data -> { + if (data.isInvalid()) { + plugin.getLocales().getLocale("error_invalid_data", data.getInvalidReason(plugin)) + .ifPresent(executor::sendMessage); + return; + } + DataSnapshotOverview.of(data.unpack(plugin), data.getFileSize(plugin), user, plugin) + .show(executor); + }, + () -> plugin.getLocales().getLocale("error_invalid_version_uuid") + .ifPresent(executor::sendMessage) ); } @@ -121,7 +125,7 @@ public class UserDataCommand extends PluginCommand { // Restore a snapshot private void restoreSnapshot(@NotNull CommandUser executor, @NotNull User user, @NotNull UUID version) { final Optional optionalData = plugin.getDatabase().getSnapshot(user, version); - if (optionalData.isEmpty()) { + if (!optionalData.isPresent()) { plugin.getLocales().getLocale("error_invalid_version_uuid") .ifPresent(executor::sendMessage); return; @@ -155,7 +159,7 @@ public class UserDataCommand extends PluginCommand { // Pin a snapshot private void pinSnapshot(@NotNull CommandUser executor, @NotNull User user, @NotNull UUID version) { final Optional optionalData = plugin.getDatabase().getSnapshot(user, version); - if (optionalData.isEmpty()) { + if (!optionalData.isPresent()) { plugin.getLocales().getLocale("error_invalid_version_uuid") .ifPresent(executor::sendMessage); return; @@ -177,7 +181,7 @@ public class UserDataCommand extends PluginCommand { private void dumpSnapshot(@NotNull CommandUser executor, @NotNull User user, @NotNull UUID version, @NotNull DumpType type) { final Optional data = plugin.getDatabase().getSnapshot(user, version); - if (data.isEmpty()) { + if (!data.isPresent()) { plugin.getLocales().getLocale("error_invalid_version_uuid") .ifPresent(executor::sendMessage); return; @@ -265,12 +269,15 @@ public class UserDataCommand extends PluginCommand { private ArgumentElement dumpType() { return new ArgumentElement<>("type", reader -> { final String type = reader.readString(); - return switch (type.toLowerCase(Locale.ENGLISH)) { - case "web" -> DumpType.WEB; - case "file" -> DumpType.FILE; - default -> throw CommandSyntaxException.BUILT_IN_EXCEPTIONS - .dispatcherUnknownArgument().createWithContext(reader); - }; + switch (type.toLowerCase(Locale.ENGLISH)) { + case "web": + return DumpType.WEB; + case "file": + return DumpType.FILE; + default: + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS + .dispatcherUnknownArgument().createWithContext(reader); + } }, (context, builder) -> { builder.suggest("web"); builder.suggest("file"); diff --git a/common/src/main/java/net/william278/husksync/config/Locales.java b/common/src/main/java/net/william278/husksync/config/Locales.java index 6c7e5002..9246210c 100644 --- a/common/src/main/java/net/william278/husksync/config/Locales.java +++ b/common/src/main/java/net/william278/husksync/config/Locales.java @@ -42,14 +42,14 @@ import java.util.Optional; @NoArgsConstructor(access = AccessLevel.PRIVATE) public class Locales { - static final String CONFIG_HEADER = """ - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ HuskSync - Locales ┃ - ┃ Developed by William278 ┃ - ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - ┣╸ See plugin about menu for international locale credits - ┣╸ Formatted in MineDown: https://github.com/Phoenix616/MineDown - ┗╸ Translate HuskSync: https://william278.net/docs/husksync/translations"""; + static final String CONFIG_HEADER = + "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n" + + "┃ HuskSync - Locales ┃\n" + + "┃ Developed by William278 ┃\n" + + "┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n" + + "┣╸ See plugin about menu for international locale credits\n" + + "┣╸ Formatted in MineDown: https://github.com/Phoenix616/MineDown\n" + + "┗╸ Translate HuskSync: https://william278.net/docs/husksync/translations"; protected static final String DEFAULT_LOCALE = "en-gb"; @@ -202,9 +202,9 @@ public class Locales { /** * Displays the notification in an Advancement Toast * - * @deprecated No longer supported + * @deprecated No longer supported, since 3.6.7 */ - @Deprecated(since = "3.6.7") + @Deprecated TOAST, /** diff --git a/common/src/main/java/net/william278/husksync/config/Server.java b/common/src/main/java/net/william278/husksync/config/Server.java index 663f8f2e..7eec4fd0 100644 --- a/common/src/main/java/net/william278/husksync/config/Server.java +++ b/common/src/main/java/net/william278/husksync/config/Server.java @@ -27,6 +27,7 @@ import lombok.NoArgsConstructor; import org.jetbrains.annotations.NotNull; import java.nio.file.Path; +import java.nio.file.Paths; @Getter @Configuration @@ -34,13 +35,13 @@ import java.nio.file.Path; @AllArgsConstructor(access = AccessLevel.PRIVATE) public class Server { - static final String CONFIG_HEADER = """ - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ HuskSync - Server ID ┃ - ┃ Developed by William278 ┃ - ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - ┣╸ This file should contain the ID of this server as defined in your proxy config. - ┗╸ If you join it using /server alpha, then set it to 'alpha' (case-sensitive)"""; + static final String CONFIG_HEADER = + "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n" + + "┃ HuskSync - Server ID ┃\n" + + "┃ Developed by William278 ┃\n" + + "┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n" + + "┣╸ This file should contain the ID of this server as defined in your proxy config.\n" + + "┗╸ If you join it using /server alpha, then set it to 'alpha' (case-sensitive)"; private String name = getDefault(); @@ -55,14 +56,14 @@ public class Server { @NotNull private static String getDefault() { final String serverFolder = System.getProperty("user.dir"); - return serverFolder == null ? "server" : Path.of(serverFolder).getFileName().toString().trim(); + return serverFolder == null ? "server" : Paths.get(serverFolder).getFileName().toString().trim(); } @Override public boolean equals(@NotNull Object other) { // If the name of this server matches another, the servers are the same. - if (other instanceof Server server) { - return server.getName().equalsIgnoreCase(this.getName()); + if (other instanceof Server) { + return ((Server) other).getName().equalsIgnoreCase(this.getName()); } return super.equals(other); } diff --git a/common/src/main/java/net/william278/husksync/config/Settings.java b/common/src/main/java/net/william278/husksync/config/Settings.java index e087ffd0..dae114e6 100644 --- a/common/src/main/java/net/william278/husksync/config/Settings.java +++ b/common/src/main/java/net/william278/husksync/config/Settings.java @@ -19,6 +19,7 @@ package net.william278.husksync.config; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import de.exlll.configlib.Comment; import de.exlll.configlib.Configuration; @@ -47,15 +48,14 @@ import java.util.Map; @NoArgsConstructor(access = AccessLevel.PRIVATE) public class Settings { - protected static final String CONFIG_HEADER = """ - ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ - ┃ HuskSync Config ┃ - ┃ Developed by William278 ┃ - ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - ┣╸ Information: https://william278.net/project/husksync - ┣╸ Config Help: https://william278.net/docs/husksync/config-file/ - ┗╸ Documentation: https://william278.net/docs/husksync"""; - + protected static final String CONFIG_HEADER = + "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n" + + "┃ HuskSync Config ┃\n" + + "┃ Developed by William278 ┃\n" + + "┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n" + + "┣╸ Information: https://william278.net/project/husksync\n" + + "┣╸ Config Help: https://william278.net/docs/husksync/config-file/\n" + + "┗╸ Documentation: https://william278.net/docs/husksync"; // Top-level settings @Comment({"Locale of the default language file to use.", "Docs: https://william278.net/docs/husksync/translations"}) private String language = Locales.DEFAULT_LOCALE; @@ -207,7 +207,7 @@ public class Settings { @Comment({"List of save cause IDs for which a snapshot will be automatically pinned (so it won't be rotated).", "Docs: https://william278.net/docs/husksync/data-rotation#save-causes"}) @Getter(AccessLevel.NONE) - private List autoPinnedSaveCauses = List.of( + private List autoPinnedSaveCauses = ImmutableList.of( DataSnapshot.SaveCause.INVENTORY_COMMAND.name(), DataSnapshot.SaveCause.ENDERCHEST_COMMAND.name(), DataSnapshot.SaveCause.BACKUP_RESTORE.name(), @@ -265,7 +265,7 @@ public class Settings { private Map features = Identifier.getConfigMap(); @Comment("Commands which should be blocked before a player has finished syncing (Use * to block all commands)") - private List blacklistedCommandsWhileLocked = new ArrayList<>(List.of("*")); + private List blacklistedCommandsWhileLocked = new ArrayList<>(ImmutableList.of("*")); @Comment("Configuration for how to sync attributes") private AttributeSettings attributes = new AttributeSettings(); @@ -278,21 +278,21 @@ public class Settings { @Comment({"Which attributes should not be saved when syncing users. Supports wildcard matching.", "(e.g. ['minecraft:generic.max_health', 'minecraft:generic.*'])"}) @Getter(AccessLevel.NONE) - private List ignoredAttributes = new ArrayList<>(List.of("")); + private List ignoredAttributes = new ArrayList<>(ImmutableList.of("")); @Comment({"Which modifiers should not be saved when syncing users. Supports wildcard matching.", "(e.g. ['minecraft:effect.speed', 'minecraft:effect.*'])"}) @Getter(AccessLevel.NONE) - private List ignoredModifiers = new ArrayList<>(List.of( + private List ignoredModifiers = new ArrayList<>(ImmutableList.of( "minecraft:effect.*", "minecraft:creative_mode_*" )); private boolean matchesWildcard(@NotNull String pat, @NotNull String value) { if (!pat.contains(":")) { - pat = "minecraft:%s".formatted(pat); + pat = "minecraft:" + pat; } if (!value.contains(":")) { - value = "minecraft:%s".formatted(value); + value = "minecraft:" + value; } return pat.contains("*") ? value.matches(pat.replace("*", ".*")) : pat.equals(value); } diff --git a/common/src/main/java/net/william278/husksync/data/Data.java b/common/src/main/java/net/william278/husksync/data/Data.java index 46074204..d0a337b4 100644 --- a/common/src/main/java/net/william278/husksync/data/Data.java +++ b/common/src/main/java/net/william278/husksync/data/Data.java @@ -25,6 +25,7 @@ import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Value; import lombok.experimental.Accessors; import net.kyori.adventure.key.Key; import net.william278.husksync.HuskSync; @@ -65,9 +66,13 @@ public interface Data { return getStack().length; } - record Stack(@NotNull String material, int amount, @Nullable String name, - @Nullable List lore, @NotNull List enchantments) { - + @Value + class Stack { + @NotNull String material; + int amount; + @Nullable String name; + @Nullable List lore; + @NotNull List enchantments; } default boolean isEmpty() { @@ -129,23 +134,32 @@ public interface Data { @NotNull List getActiveEffects(); - /** - * Represents a potion effect - * - * @param type the type of potion effect - * @param amplifier the amplifier of the potion effect - * @param duration the duration of the potion effect - * @param isAmbient whether the potion effect is ambient - * @param showParticles whether the potion effect shows particles - * @param hasIcon whether the potion effect displays a HUD icon - */ - record Effect(@SerializedName("type") @NotNull String type, - @SerializedName("amplifier") int amplifier, - @SerializedName("duration") int duration, - @SerializedName("is_ambient") boolean isAmbient, - @SerializedName("show_particles") boolean showParticles, - @SerializedName("has_icon") boolean hasIcon) { - + @Value + class Effect { + /** + * The type of potion effect + */ + @SerializedName("type") @NotNull String type; + /** + * The amplifier of the potion effect + */ + @SerializedName("amplifier") int amplifier; + /** + * The duration of the potion effect + */ + @SerializedName("duration") int duration; + /** + * Whether the potion effect is ambient + */ + @SerializedName("is_ambient") boolean isAmbient; + /** + * Whether the potion effect shows particles + */ + @SerializedName("show_particles") boolean showParticles; + /** + * Whether the potion effect displays a HUD icon + */ + @SerializedName("has_icon") boolean hasIcon; } } @@ -162,7 +176,9 @@ public interface Data { @NotNull default List getCompletedExcludingRecipes() { - return getCompleted().stream().filter(adv -> !adv.getKey().startsWith(RECIPE_ADVANCEMENT)).toList(); + return getCompleted().stream() + .filter(adv -> !adv.getKey().startsWith(RECIPE_ADVANCEMENT)) + .collect(Collectors.toList()); } void setCompleted(@NotNull List completed); @@ -249,11 +265,11 @@ public interface Data { void setWorld(@NotNull World world); - record World( - @SerializedName("name") @NotNull String name, - @SerializedName("uuid") @NotNull UUID uuid, - @SerializedName("environment") @NotNull String environment - ) { + @Value + class World { + @SerializedName("name") @NotNull String name; + @SerializedName("uuid") @NotNull UUID uuid; + @SerializedName("environment") @NotNull String environment; } } @@ -295,17 +311,17 @@ public interface Data { void setHealth(double health); /** - * @deprecated Use {@link Attributes#getMaxHealth()} instead + * @deprecated Use {@link Attributes#getMaxHealth()} instead, since 3.5 */ - @Deprecated(forRemoval = true, since = "3.5") + @Deprecated default double getMaxHealth() { return getHealth(); } /** - * @deprecated Use {@link Attributes#setMaxHealth(double)} instead + * @deprecated Use {@link Attributes#setMaxHealth(double)} instead, since 3.5 */ - @Deprecated(forRemoval = true, since = "3.5") + @Deprecated default void setMaxHealth(double maxHealth) { } @@ -323,11 +339,11 @@ public interface Data { List getAttributes(); - record Attribute( - @NotNull String name, - double baseValue, - @NotNull Set modifiers - ) { + @Value + class Attribute { + @NotNull String name; + double baseValue; + @NotNull Set modifiers; public double getValue() { double value = baseValue; @@ -366,7 +382,8 @@ public interface Data { @Override public boolean equals(Object obj) { - if (obj instanceof Modifier other) { + if (obj instanceof Modifier) { + Modifier other = (Modifier) obj; if (uuid == null || other.uuid == null) { return name.equals(other.name); } @@ -376,12 +393,16 @@ public interface Data { } public double modify(double value) { - return switch (operationType) { - case 0 -> value + amount; - case 1 -> value * amount; - case 2 -> value * (1 + amount); - default -> value; - }; + switch (operationType) { + case 0: + return value + amount; + case 1: + return value * amount; + case 2: + return value * (1 + amount); + default: + return value; + } } public boolean hasUuid() { @@ -397,12 +418,12 @@ public interface Data { default Optional getAttribute(@NotNull Key key) { return getAttributes().stream() - .filter(attribute -> attribute.name().equals(key.asString())) + .filter(attribute -> attribute.getName().equals(key.asString())) .findFirst(); } default void removeAttribute(@NotNull Key key) { - getAttributes().removeIf(attribute -> attribute.name().equals(key.asString())); + getAttributes().removeIf(attribute -> attribute.getName().equals(key.asString())); } default double getMaxHealth() { @@ -481,9 +502,9 @@ public interface Data { * * @return {@code false} since v3.5 * @deprecated Moved to its own data type. This will always return {@code false}. - * Use {@link FlightStatus#isAllowFlight()} instead + * Use {@link FlightStatus#isAllowFlight()} instead, since 3.5 */ - @Deprecated(forRemoval = true, since = "3.5") + @Deprecated default boolean getAllowFlight() { return false; } @@ -492,9 +513,9 @@ public interface Data { * Set if the player can fly. * * @deprecated Moved to its own data type. - * Use {@link FlightStatus#setAllowFlight(boolean)} instead + * Use {@link FlightStatus#setAllowFlight(boolean)} instead, since 3.5 */ - @Deprecated(forRemoval = true, since = "3.5") + @Deprecated default void setAllowFlight(boolean allowFlight) { } @@ -503,9 +524,9 @@ public interface Data { * * @return {@code false} since v3.5 * @deprecated Moved to its own data type. This will always return {@code false}. - * Use {@link FlightStatus#isFlying()} instead + * Use {@link FlightStatus#isFlying()} instead, since 3.5 */ - @Deprecated(forRemoval = true, since = "3.5") + @Deprecated default boolean getIsFlying() { return false; } @@ -514,9 +535,9 @@ public interface Data { * Set if the player is flying. * * @deprecated Moved to its own data type. - * Use {@link FlightStatus#setFlying(boolean)} instead + * Use {@link FlightStatus#setFlying(boolean)} instead, since 3.5 */ - @Deprecated(forRemoval = true, since = "3.5") + @Deprecated default void setIsFlying(boolean isFlying) { } diff --git a/common/src/main/java/net/william278/husksync/data/DataSnapshot.java b/common/src/main/java/net/william278/husksync/data/DataSnapshot.java index 2068bc30..528994d2 100644 --- a/common/src/main/java/net/william278/husksync/data/DataSnapshot.java +++ b/common/src/main/java/net/william278/husksync/data/DataSnapshot.java @@ -383,7 +383,7 @@ public class DataSnapshot { private Unpacked(@NotNull UUID id, boolean pinned, @NotNull OffsetDateTime timestamp, @NotNull String saveCause, @NotNull String serverName, @NotNull TreeMap data, @NotNull Version minecraftVersion, @NotNull String platformType, int formatVersion) { - super(id, pinned, timestamp, saveCause, serverName, Map.of(), minecraftVersion, platformType, formatVersion); + super(id, pinned, timestamp, saveCause, serverName, Collections.emptyMap(), minecraftVersion, platformType, formatVersion); this.deserialized = data; } @@ -392,7 +392,10 @@ public class DataSnapshot { private TreeMap deserializeData(@NotNull HuskSync plugin) { return data.entrySet().stream() .filter(e -> plugin.getIdentifier(e.getKey()).isPresent()) - .map(entry -> Map.entry(plugin.getIdentifier(entry.getKey()).orElseThrow(), entry.getValue())) + .map(entry -> Maps.immutableEntry( + plugin.getIdentifier(entry.getKey()).orElseThrow(() -> new IllegalStateException("Invalid identifier")), + entry.getValue() + )) .collect(Collectors.toMap( Map.Entry::getKey, entry -> plugin.deserializeData(entry.getKey(), entry.getValue(), getMinecraftVersion()), @@ -939,7 +942,7 @@ public class DataSnapshot { @NotNull public String getLocale(@NotNull HuskSync plugin) { return plugin.getLocales() - .getRawLocale("save_cause_%s".formatted(name().toLowerCase(Locale.ENGLISH))) + .getRawLocale("save_cause_" + name().toLowerCase(Locale.ENGLISH)) .orElse(getDisplayName()); } diff --git a/common/src/main/java/net/william278/husksync/data/Identifier.java b/common/src/main/java/net/william278/husksync/data/Identifier.java index bf915f9a..5e2efbb3 100644 --- a/common/src/main/java/net/william278/husksync/data/Identifier.java +++ b/common/src/main/java/net/william278/husksync/data/Identifier.java @@ -19,6 +19,9 @@ package net.william278.husksync.data; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; import lombok.*; import net.kyori.adventure.key.InvalidKeyException; import net.kyori.adventure.key.Key; @@ -150,7 +153,7 @@ public class Identifier { private static Identifier huskSync(@Subst("null") @NotNull String name, @SuppressWarnings("SameParameterValue") boolean configDefault, @NotNull Dependency... dependents) throws InvalidKeyException { - return new Identifier(Key.key("husksync", name), configDefault, Set.of(dependents)); + return new Identifier(Key.key("husksync", name), configDefault, ImmutableSet.copyOf(dependents)); } /** @@ -163,7 +166,7 @@ public class Identifier { @ApiStatus.Internal @SuppressWarnings("unchecked") public static Map getConfigMap() { - return Map.ofEntries(Stream.of( + return ImmutableMap.ofEntries(Stream.of( INVENTORY, ENDER_CHEST, POTION_EFFECTS, ADVANCEMENTS, LOCATION, STATISTICS, HEALTH, HUNGER, ATTRIBUTES, EXPERIENCE, GAME_MODE, FLIGHT_STATUS, PERSISTENT_DATA ) @@ -230,7 +233,8 @@ public class Identifier { */ @Override public boolean equals(Object obj) { - if (obj instanceof Identifier other) { + if (obj instanceof Identifier) { + Identifier other = (Identifier) obj; return key.equals(other.key); } return false; @@ -239,7 +243,7 @@ public class Identifier { // Get the config entry for the identifier @NotNull private Map.Entry getConfigEntry() { - return Map.entry(getKeyValue(), enabledByDefault); + return Maps.immutableEntry(getKeyValue(), enabledByDefault); } /** @@ -260,7 +264,7 @@ public class Identifier { if (i1.dependsOn(i2)) { if (i2.dependsOn(i1)) { throw new IllegalArgumentException( - "Found circular dependency between %s and %s".formatted(i1.getKey(), i2.getKey()) + "Found circular dependency between " + i1.getKey() + " and " + i2.getKey() ); } return 1; @@ -310,7 +314,8 @@ public class Identifier { @Override public boolean equals(Object obj) { - if (obj instanceof Dependency other) { + if (obj instanceof Dependency) { + Dependency other = (Dependency) obj; return key.equals(other.key); } return false; diff --git a/common/src/main/java/net/william278/husksync/data/SerializerRegistry.java b/common/src/main/java/net/william278/husksync/data/SerializerRegistry.java index 32e46a21..7157ed83 100644 --- a/common/src/main/java/net/william278/husksync/data/SerializerRegistry.java +++ b/common/src/main/java/net/william278/husksync/data/SerializerRegistry.java @@ -26,6 +26,7 @@ import org.jetbrains.annotations.NotNull; import java.util.*; import java.util.logging.Level; +import java.util.stream.Collectors; public interface SerializerRegistry { @@ -52,7 +53,7 @@ public interface SerializerRegistry { @SuppressWarnings("unchecked") default void registerSerializer(@NotNull Identifier id, @NotNull Serializer serializer) { if (id.isCustom()) { - getPlugin().log(Level.INFO, "Registered custom data type: %s".formatted(id)); + getPlugin().log(Level.INFO, "Registered custom data type: " + id); } id.setEnabled(id.isCustom() || getPlugin().getSettings().getSynchronization().isFeatureEnabled(id)); getSerializers().put(id, (Serializer) serializer); @@ -72,11 +73,11 @@ public interface SerializerRegistry { final List unmet = identifier.getDependencies().stream() .filter(Identifier.Dependency::isRequired) .filter(dep -> !isDataTypeAvailable(dep.getKey().asString())) - .map(dep -> dep.getKey().asString()).toList(); + .map(dep -> dep.getKey().asString()) + .collect(Collectors.toList()); if (!unmet.isEmpty()) { identifier.setEnabled(false); - getPlugin().log(Level.WARNING, "Disabled %s syncing as the following types need to be on: %s" - .formatted(identifier, String.join(", ", unmet))); + getPlugin().log(Level.WARNING, "Disabled " + identifier + " syncing as the following types need to be on: " + String.join(", ", unmet)); } }); } @@ -116,7 +117,7 @@ public interface SerializerRegistry { @NotNull default String serializeData(@NotNull Identifier identifier, @NotNull Data data) throws IllegalStateException { return getSerializer(identifier).map(serializer -> serializer.serialize(data)) - .orElseThrow(() -> new IllegalStateException("No serializer found for %s".formatted(identifier))); + .orElseThrow(() -> new IllegalStateException("No serializer found for " + identifier)); } /** @@ -133,7 +134,7 @@ public interface SerializerRegistry { default Data deserializeData(@NotNull Identifier identifier, @NotNull String data, @NotNull Version dataMcVersion) throws IllegalStateException { return getSerializer(identifier).map(serializer -> serializer.deserialize(data, dataMcVersion)).orElseThrow( - () -> new IllegalStateException("No serializer found for %s".formatted(identifier)) + () -> new IllegalStateException("No serializer found for " + identifier) ); } @@ -144,10 +145,10 @@ public interface SerializerRegistry { * @param data the data to deserialize * @return the deserialized data * @since 3.5.4 - * @deprecated Use {@link #deserializeData(Identifier, String, Version)} instead + * @deprecated Use {@link #deserializeData(Identifier, String, Version)} instead, since 3.6.5 */ @NotNull - @Deprecated(since = "3.6.5") + @Deprecated default Data deserializeData(@NotNull Identifier identifier, @NotNull String data) { return deserializeData(identifier, data, getPlugin().getMinecraftVersion()); } @@ -164,7 +165,7 @@ public interface SerializerRegistry { } // Returns if a data type is available and enabled in the config - private boolean isDataTypeAvailable(@NotNull String key) { + default boolean isDataTypeAvailable(@NotNull String key) { return getIdentifier(key).map(Identifier::isEnabled).orElse(false); } diff --git a/common/src/main/java/net/william278/husksync/data/UserDataHolder.java b/common/src/main/java/net/william278/husksync/data/UserDataHolder.java index 5f1cb9ff..0e75f22d 100644 --- a/common/src/main/java/net/william278/husksync/data/UserDataHolder.java +++ b/common/src/main/java/net/william278/husksync/data/UserDataHolder.java @@ -19,6 +19,7 @@ package net.william278.husksync.data; +import com.google.common.collect.Maps; import net.william278.desertwell.util.ThrowingConsumer; import net.william278.husksync.HuskSync; import org.jetbrains.annotations.ApiStatus; @@ -44,7 +45,7 @@ public interface UserDataHolder extends DataHolder { default Map getData() { return getPlugin().getRegisteredDataTypes().stream() .filter(Identifier::isEnabled) - .map(id -> Map.entry(id, getData(id))) + .map(id -> Maps.immutableEntry(id, getData(id))) .filter(data -> data.getValue().isPresent()) .collect(HashMap::new, (map, data) -> map.put(data.getKey(), data.getValue().get()), HashMap::putAll); } diff --git a/common/src/main/java/net/william278/husksync/database/Database.java b/common/src/main/java/net/william278/husksync/database/Database.java index 732b641f..ebd1c0bb 100644 --- a/common/src/main/java/net/william278/husksync/database/Database.java +++ b/common/src/main/java/net/william278/husksync/database/Database.java @@ -19,11 +19,14 @@ package net.william278.husksync.database; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; import lombok.Getter; import net.william278.husksync.HuskSync; import net.william278.husksync.config.Settings; import net.william278.husksync.data.DataSnapshot; import net.william278.husksync.user.User; +import net.william278.husksync.util.InputStreamUtil; import org.jetbrains.annotations.Blocking; import org.jetbrains.annotations.NotNull; @@ -56,8 +59,10 @@ public abstract class Database { @SuppressWarnings("SameParameterValue") @NotNull protected final String[] getSchemaStatements(@NotNull String schemaFileName) throws IOException { - return formatStatementTables(new String(Objects.requireNonNull(plugin.getResource(schemaFileName)) - .readAllBytes(), StandardCharsets.UTF_8)).split(";"); + return formatStatementTables(new String( + InputStreamUtil.readAllBytes(Objects.requireNonNull(plugin.getResource(schemaFileName))), + StandardCharsets.UTF_8 + )).split(";"); } /** @@ -285,13 +290,13 @@ public abstract class Database { @NotNull private Map.Entry toEntry() { - return Map.entry(name().toLowerCase(Locale.ENGLISH), defaultName); + return Maps.immutableEntry(name().toLowerCase(Locale.ENGLISH), defaultName); } @SuppressWarnings("unchecked") @NotNull public static Map getDefaults() { - return Map.ofEntries(Arrays.stream(values()) + return ImmutableMap.ofEntries(Arrays.stream(values()) .map(TableName::toEntry) .toArray(Map.Entry[]::new)); } diff --git a/common/src/main/java/net/william278/husksync/database/MongoDbDatabase.java b/common/src/main/java/net/william278/husksync/database/MongoDbDatabase.java index 08da5091..7a79b6d9 100644 --- a/common/src/main/java/net/william278/husksync/database/MongoDbDatabase.java +++ b/common/src/main/java/net/william278/husksync/database/MongoDbDatabase.java @@ -30,6 +30,7 @@ import net.william278.husksync.data.DataSnapshot; import net.william278.husksync.database.mongo.MongoCollectionHelper; import net.william278.husksync.database.mongo.MongoConnectionHandler; import net.william278.husksync.user.User; +import net.william278.husksync.util.OptionalUtil; import org.bson.Document; import org.bson.conversions.Bson; import org.bson.types.Binary; @@ -43,6 +44,7 @@ import java.util.Optional; import java.util.TimeZone; import java.util.UUID; import java.util.logging.Level; +import java.util.stream.Collectors; public class MongoDbDatabase extends Database { private MongoConnectionHandler mongoConnectionHandler; @@ -103,7 +105,7 @@ public class MongoDbDatabase extends Database { @Override public void ensureUser(@NotNull User user) { try { - getUser(user.getUuid()).ifPresentOrElse( + OptionalUtil.ifPresentOrElse(getUser(user.getUuid()), existingUser -> { if (!existingUser.getUsername().equals(user.getUsername())) { // Update a user's name if it has changed in the database @@ -277,7 +279,8 @@ public class MongoDbDatabase extends Database { protected void rotateSnapshots(@NotNull User user) { try { final List unpinnedUserData = getAllSnapshots(user).stream() - .filter(dataSnapshot -> !dataSnapshot.isPinned()).toList(); + .filter(dataSnapshot -> !dataSnapshot.isPinned()) + .collect(Collectors.toList()); final int maxSnapshots = plugin.getSettings().getSynchronization().getMaxUserDataSnapshots(); if (unpinnedUserData.size() > maxSnapshots) { diff --git a/common/src/main/java/net/william278/husksync/database/MySqlDatabase.java b/common/src/main/java/net/william278/husksync/database/MySqlDatabase.java index f3b38dc1..9751f274 100644 --- a/common/src/main/java/net/william278/husksync/database/MySqlDatabase.java +++ b/common/src/main/java/net/william278/husksync/database/MySqlDatabase.java @@ -20,11 +20,12 @@ package net.william278.husksync.database; import com.google.common.collect.Lists; -import com.zaxxer.hikari.HikariDataSource; import net.william278.husksync.HuskSync; import net.william278.husksync.adapter.DataAdapter; import net.william278.husksync.data.DataSnapshot; import net.william278.husksync.user.User; +import net.william278.husksync.util.OptionalUtil; +import org.inksnow.cputil.db.AuroraDatabase; import org.jetbrains.annotations.Blocking; import org.jetbrains.annotations.NotNull; @@ -34,6 +35,7 @@ import java.sql.*; import java.time.OffsetDateTime; import java.util.*; import java.util.logging.Level; +import java.util.stream.Collectors; import static net.william278.husksync.config.Settings.DatabaseSettings; @@ -41,15 +43,17 @@ public class MySqlDatabase extends Database { private static final String DATA_POOL_NAME = "HuskSyncHikariPool"; private final String flavor; - private final String driverClass; - private HikariDataSource dataSource; + private final org.inksnow.cputil.db.Database databaseType; + private AuroraDatabase dataSource; public MySqlDatabase(@NotNull HuskSync plugin) { super(plugin); final Type type = plugin.getSettings().getDatabase().getType(); this.flavor = type.getProtocol(); - this.driverClass = type == Type.MARIADB ? "org.mariadb.jdbc.Driver" : "com.mysql.cj.jdbc.Driver"; + this.databaseType = type == Type.MARIADB + ? new org.inksnow.cputil.db.mariadb.MariadbDatabase() + : new org.inksnow.cputil.db.mysql.MysqlDatabase(); } /** @@ -72,48 +76,44 @@ public class MySqlDatabase extends Database { public void initialize() throws IllegalStateException { // Initialize the Hikari pooled connection final DatabaseSettings.DatabaseCredentials credentials = plugin.getSettings().getDatabase().getCredentials(); - dataSource = new HikariDataSource(); - dataSource.setDriverClassName(driverClass); - dataSource.setJdbcUrl(String.format("jdbc:%s://%s:%s/%s%s", - flavor, - credentials.getHost(), - credentials.getPort(), - credentials.getDatabase(), - credentials.getParameters() - )); - - // Authenticate with the database - dataSource.setUsername(credentials.getUsername()); - dataSource.setPassword(credentials.getPassword()); - - // Set connection pool options - final DatabaseSettings.PoolSettings pool = plugin.getSettings().getDatabase().getConnectionPool(); - dataSource.setMaximumPoolSize(pool.getMaximumPoolSize()); - dataSource.setMinimumIdle(pool.getMinimumIdle()); - dataSource.setMaxLifetime(pool.getMaximumLifetime()); - dataSource.setKeepaliveTime(pool.getKeepaliveTime()); - dataSource.setConnectionTimeout(pool.getConnectionTimeout()); - dataSource.setPoolName(DATA_POOL_NAME); - - // Set additional connection pool properties - final Properties properties = new Properties(); - properties.putAll( - Map.of("cachePrepStmts", "true", - "prepStmtCacheSize", "250", - "prepStmtCacheSqlLimit", "2048", - "useServerPrepStmts", "true", - "useLocalSessionState", "true", - "useLocalTransactionState", "true" - )); - properties.putAll( - Map.of( - "rewriteBatchedStatements", "true", - "cacheResultSetMetadata", "true", - "cacheServerConfiguration", "true", - "elideSetAutoCommits", "true", - "maintainTimeStats", "false") - ); - dataSource.setDataSourceProperties(properties); + + try { + dataSource = AuroraDatabase.builder() + .databaseType(databaseType) + .jdbcUrl(String.format("jdbc:%s://%s:%s/%s%s", + flavor, + credentials.getHost(), + credentials.getPort(), + credentials.getDatabase(), + credentials.getParameters() + )) + .username(credentials.getUsername()) + .password(credentials.getPassword()) + .extension(dataSource -> { + final DatabaseSettings.PoolSettings pool = plugin.getSettings().getDatabase().getConnectionPool(); + dataSource.setMaximumPoolSize(pool.getMaximumPoolSize()); + dataSource.setMinimumIdle(pool.getMinimumIdle()); + dataSource.setMaxLifetime(pool.getMaximumLifetime()); + dataSource.setKeepaliveTime(pool.getKeepaliveTime()); + dataSource.setConnectionTimeout(pool.getConnectionTimeout()); + dataSource.setPoolName(DATA_POOL_NAME); + }) + .driverProperty("cachePrepStmts", "true") + .driverProperty("prepStmtCacheSize", "250") + .driverProperty("prepStmtCacheSqlLimit", "2048") + .driverProperty("useServerPrepStmts", "true") + .driverProperty("useLocalSessionState", "true") + .driverProperty("useLocalTransactionState", "true") + + .driverProperty("rewriteBatchedStatements", "true") + .driverProperty("cacheResultSetMetadata", "true") + .driverProperty("cacheServerConfiguration", "true") + .driverProperty("elideSetAutoCommits", "true") + .driverProperty("maintainTimeStats", "false") + .build(); + } catch (IOException e) { + throw new IllegalStateException("Failed to initialize the Aurora database", e); + } // Prepare database schema; make tables if they don't exist try (Connection connection = dataSource.getConnection()) { @@ -135,15 +135,14 @@ public class MySqlDatabase extends Database { @Blocking @Override public void ensureUser(@NotNull User user) { - getUser(user.getUuid()).ifPresentOrElse( + OptionalUtil.ifPresentOrElse(getUser(user.getUuid()), existingUser -> { if (!existingUser.getUsername().equals(user.getUsername())) { // Update a user's name if it has changed in the database try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - UPDATE `%users_table%` - SET `username`=? - WHERE `uuid`=?"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "UPDATE `%users_table%` SET `username`=? WHERE `uuid`=?" + ))) { statement.setString(1, user.getUsername()); statement.setString(2, existingUser.getUuid().toString()); @@ -158,9 +157,9 @@ public class MySqlDatabase extends Database { () -> { // Insert new player data into the database try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - INSERT INTO `%users_table%` (`uuid`,`username`) - VALUES (?,?);"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "INSERT INTO `%users_table%` (`uuid`,`username`) VALUES (?,?);" + ))) { statement.setString(1, user.getUuid().toString()); statement.setString(2, user.getUsername()); @@ -177,10 +176,9 @@ public class MySqlDatabase extends Database { @Override public Optional getUser(@NotNull UUID uuid) { try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - SELECT `uuid`, `username` - FROM `%users_table%` - WHERE `uuid`=?"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "SELECT `uuid`, `username` FROM `%users_table%` WHERE `uuid`=?" + ))) { statement.setString(1, uuid.toString()); @@ -200,10 +198,9 @@ public class MySqlDatabase extends Database { @Override public Optional getUserByName(@NotNull String username) { try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - SELECT `uuid`, `username` - FROM `%users_table%` - WHERE `username`=?"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "SELECT `uuid`, `username` FROM `%users_table%` WHERE `username`=?" + ))) { statement.setString(1, username); final ResultSet resultSet = statement.executeQuery(); @@ -222,12 +219,13 @@ public class MySqlDatabase extends Database { @Override public Optional getLatestSnapshot(@NotNull User user) { try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - SELECT `version_uuid`, `timestamp`, `data` - FROM `%user_data_table%` - WHERE `player_uuid`=? - ORDER BY `timestamp` DESC - LIMIT 1;"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "SELECT `version_uuid`, `timestamp`, `data` " + + "FROM `%user_data_table%` " + + "WHERE `player_uuid`=? " + + "ORDER BY `timestamp` DESC " + + "LIMIT 1;" + ))) { statement.setString(1, user.getUuid().toString()); final ResultSet resultSet = statement.executeQuery(); if (resultSet.next()) { @@ -253,11 +251,12 @@ public class MySqlDatabase extends Database { public List getAllSnapshots(@NotNull User user) { final List retrievedData = Lists.newArrayList(); try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - SELECT `version_uuid`, `timestamp`, `data` - FROM `%user_data_table%` - WHERE `player_uuid`=? - ORDER BY `timestamp` DESC;"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "SELECT `version_uuid`, `timestamp`, `data` " + + "FROM `%user_data_table%` " + + "WHERE `player_uuid`=? " + + "ORDER BY `timestamp` DESC;" + ))) { statement.setString(1, user.getUuid().toString()); final ResultSet resultSet = statement.executeQuery(); while (resultSet.next()) { @@ -282,12 +281,13 @@ public class MySqlDatabase extends Database { @Override public Optional getSnapshot(@NotNull User user, @NotNull UUID versionUuid) { try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - SELECT `version_uuid`, `timestamp`, `data` - FROM `%user_data_table%` - WHERE `player_uuid`=? AND `version_uuid`=? - ORDER BY `timestamp` DESC - LIMIT 1;"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "SELECT `version_uuid`, `timestamp`, `data` " + + "FROM `%user_data_table%` " + + "WHERE `player_uuid`=? AND `version_uuid`=? " + + "ORDER BY `timestamp` DESC " + + "LIMIT 1;" + ))) { statement.setString(1, user.getUuid().toString()); statement.setString(2, versionUuid.toString()); final ResultSet resultSet = statement.executeQuery(); @@ -311,17 +311,17 @@ public class MySqlDatabase extends Database { @Override protected void rotateSnapshots(@NotNull User user) { final List unpinnedUserData = getAllSnapshots(user).stream() - .filter(dataSnapshot -> !dataSnapshot.isPinned()).toList(); + .filter(dataSnapshot -> !dataSnapshot.isPinned()) + .collect(Collectors.toList()); final int maxSnapshots = plugin.getSettings().getSynchronization().getMaxUserDataSnapshots(); if (unpinnedUserData.size() > maxSnapshots) { try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - DELETE FROM `%user_data_table%` - WHERE `player_uuid`=? - AND `pinned` IS FALSE - ORDER BY `timestamp` ASC - LIMIT %entry_count%;""".replace("%entry_count%", - Integer.toString(unpinnedUserData.size() - maxSnapshots))))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "DELETE FROM `%user_data_table%` " + + "WHERE `player_uuid`=? " + + "AND `pinned` IS FALSE " + + "ORDER BY `timestamp` ASC " + + "LIMIT " + (unpinnedUserData.size() - maxSnapshots) + ";"))) { statement.setString(1, user.getUuid().toString()); statement.executeUpdate(); } @@ -335,10 +335,11 @@ public class MySqlDatabase extends Database { @Override public boolean deleteSnapshot(@NotNull User user, @NotNull UUID versionUuid) { try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - DELETE FROM `%user_data_table%` - WHERE `player_uuid`=? AND `version_uuid`=? - LIMIT 1;"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "DELETE FROM `%user_data_table%` " + + "WHERE `player_uuid`=? AND `version_uuid`=? " + + "LIMIT 1;" + ))) { statement.setString(1, user.getUuid().toString()); statement.setString(2, versionUuid.toString()); return statement.executeUpdate() > 0; @@ -353,11 +354,12 @@ public class MySqlDatabase extends Database { @Override protected void rotateLatestSnapshot(@NotNull User user, @NotNull OffsetDateTime within) { try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - DELETE FROM `%user_data_table%` - WHERE `player_uuid`=? AND `timestamp`>? AND `pinned` IS FALSE - ORDER BY `timestamp` ASC - LIMIT 1;"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "DELETE FROM `%user_data_table%` " + + "WHERE `player_uuid`=? AND `timestamp`>? AND `pinned` IS FALSE " + + "ORDER BY `timestamp` ASC " + + "LIMIT 1;" + ))) { statement.setString(1, user.getUuid().toString()); statement.setTimestamp(2, Timestamp.from(within.toInstant())); statement.executeUpdate(); @@ -371,10 +373,11 @@ public class MySqlDatabase extends Database { @Override protected void createSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) { try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - INSERT INTO `%user_data_table%` - (`player_uuid`,`version_uuid`,`timestamp`,`save_cause`,`pinned`,`data`) - VALUES (?,?,?,?,?,?);"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "INSERT INTO `%user_data_table%` " + + "(`player_uuid`,`version_uuid`,`timestamp`,`save_cause`,`pinned`,`data`) " + + "VALUES (?,?,?,?,?,?);" + ))) { statement.setString(1, user.getUuid().toString()); statement.setString(2, data.getId().toString()); statement.setTimestamp(3, Timestamp.from(data.getTimestamp().toInstant())); @@ -392,11 +395,12 @@ public class MySqlDatabase extends Database { @Override public void updateSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) { try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - UPDATE `%user_data_table%` - SET `save_cause`=?,`pinned`=?,`data`=? - WHERE `player_uuid`=? AND `version_uuid`=? - LIMIT 1;"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "UPDATE `%user_data_table%` " + + "SET `save_cause`=?,`pinned`=?,`data`=? " + + "WHERE `player_uuid`=? AND `version_uuid`=? " + + "LIMIT 1;" + ))) { statement.setString(1, data.getSaveCause().name()); statement.setBoolean(2, data.isPinned()); statement.setBlob(3, new ByteArrayInputStream(data.asBytes(plugin))); diff --git a/common/src/main/java/net/william278/husksync/database/PostgresDatabase.java b/common/src/main/java/net/william278/husksync/database/PostgresDatabase.java index f414d2f5..dc554dfc 100644 --- a/common/src/main/java/net/william278/husksync/database/PostgresDatabase.java +++ b/common/src/main/java/net/william278/husksync/database/PostgresDatabase.java @@ -25,6 +25,8 @@ import net.william278.husksync.HuskSync; import net.william278.husksync.adapter.DataAdapter; import net.william278.husksync.data.DataSnapshot; import net.william278.husksync.user.User; +import net.william278.husksync.util.OptionalUtil; +import org.inksnow.cputil.db.AuroraDatabase; import org.jetbrains.annotations.Blocking; import org.jetbrains.annotations.NotNull; @@ -33,6 +35,7 @@ import java.sql.*; import java.time.OffsetDateTime; import java.util.*; import java.util.logging.Level; +import java.util.stream.Collectors; import static net.william278.husksync.config.Settings.DatabaseSettings; @@ -40,7 +43,7 @@ public class PostgresDatabase extends Database { private static final String DATA_POOL_NAME = "HuskSyncHikariPool"; private final String flavor; - private final String driverClass; + private final org.inksnow.cputil.db.Database databaseType; private HikariDataSource dataSource; public PostgresDatabase(@NotNull HuskSync plugin) { @@ -48,7 +51,7 @@ public class PostgresDatabase extends Database { final Type type = plugin.getSettings().getDatabase().getType(); this.flavor = type.getProtocol(); - this.driverClass = "org.postgresql.Driver"; + this.databaseType = new org.inksnow.cputil.db.postgres.PostgresqlDatabase(); } /** @@ -71,48 +74,44 @@ public class PostgresDatabase extends Database { public void initialize() throws IllegalStateException { // Initialize the Hikari pooled connection final DatabaseSettings.DatabaseCredentials credentials = plugin.getSettings().getDatabase().getCredentials(); - dataSource = new HikariDataSource(); - dataSource.setDriverClassName(driverClass); - dataSource.setJdbcUrl(String.format("jdbc:%s://%s:%s/%s%s", - flavor, - credentials.getHost(), - credentials.getPort(), - credentials.getDatabase(), - credentials.getParameters() - )); - - // Authenticate with the database - dataSource.setUsername(credentials.getUsername()); - dataSource.setPassword(credentials.getPassword()); - - // Set connection pool options - final DatabaseSettings.PoolSettings pool = plugin.getSettings().getDatabase().getConnectionPool(); - dataSource.setMaximumPoolSize(pool.getMaximumPoolSize()); - dataSource.setMinimumIdle(pool.getMinimumIdle()); - dataSource.setMaxLifetime(pool.getMaximumLifetime()); - dataSource.setKeepaliveTime(pool.getKeepaliveTime()); - dataSource.setConnectionTimeout(pool.getConnectionTimeout()); - dataSource.setPoolName(DATA_POOL_NAME); - - // Set additional connection pool properties - final Properties properties = new Properties(); - properties.putAll( - Map.of("cachePrepStmts", "true", - "prepStmtCacheSize", "250", - "prepStmtCacheSqlLimit", "2048", - "useServerPrepStmts", "true", - "useLocalSessionState", "true", - "useLocalTransactionState", "true" - )); - properties.putAll( - Map.of( - "rewriteBatchedStatements", "true", - "cacheResultSetMetadata", "true", - "cacheServerConfiguration", "true", - "elideSetAutoCommits", "true", - "maintainTimeStats", "false") - ); - dataSource.setDataSourceProperties(properties); + + try { + dataSource = AuroraDatabase.builder() + .databaseType(databaseType) + .jdbcUrl(String.format("jdbc:%s://%s:%s/%s%s", + flavor, + credentials.getHost(), + credentials.getPort(), + credentials.getDatabase(), + credentials.getParameters() + )) + .extension(dataSource -> { + final DatabaseSettings.PoolSettings pool = plugin.getSettings().getDatabase().getConnectionPool(); + dataSource.setMaximumPoolSize(pool.getMaximumPoolSize()); + dataSource.setMinimumIdle(pool.getMinimumIdle()); + dataSource.setMaxLifetime(pool.getMaximumLifetime()); + dataSource.setKeepaliveTime(pool.getKeepaliveTime()); + dataSource.setConnectionTimeout(pool.getConnectionTimeout()); + dataSource.setPoolName(DATA_POOL_NAME); + }) + .username(credentials.getUsername()) + .password(credentials.getPassword()) + .driverProperty("cachePrepStmts", "true") + .driverProperty("prepStmtCacheSize", "250") + .driverProperty("prepStmtCacheSqlLimit", "2048") + .driverProperty("useServerPrepStmts", "true") + .driverProperty("useLocalSessionState", "true") + .driverProperty("useLocalTransactionState", "true") + + .driverProperty("rewriteBatchedStatements", "true") + .driverProperty("cacheResultSetMetadata", "true") + .driverProperty("cacheServerConfiguration", "true") + .driverProperty("elideSetAutoCommits", "true") + .driverProperty("maintainTimeStats", "false") + .build(); + } catch (IOException e) { + throw new IllegalStateException("Failed to initialize the Aurora database", e); + } // Prepare database schema; make tables if they don't exist try (Connection connection = dataSource.getConnection()) { @@ -134,15 +133,16 @@ public class PostgresDatabase extends Database { @Blocking @Override public void ensureUser(@NotNull User user) { - getUser(user.getUuid()).ifPresentOrElse( + OptionalUtil.ifPresentOrElse(getUser(user.getUuid()), existingUser -> { if (!existingUser.getUsername().equals(user.getUsername())) { // Update a user's name if it has changed in the database try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - UPDATE "%users_table%" - SET "username"=? - WHERE "uuid"=?"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "UPDATE \"%users_table%\" " + + "SET \"username\"=? " + + "WHERE \"uuid\"=?" + ))) { statement.setString(1, user.getUsername()); statement.setObject(2, existingUser.getUuid()); @@ -157,9 +157,10 @@ public class PostgresDatabase extends Database { () -> { // Insert new player data into the database try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - INSERT INTO "%users_table%" ("uuid","username") - VALUES (?,?);"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "INSERT INTO \"%users_table%\" (\"uuid\",\"username\") " + + "VALUES (?,?);" + ))) { statement.setObject(1, user.getUuid()); statement.setString(2, user.getUsername()); @@ -176,10 +177,11 @@ public class PostgresDatabase extends Database { @Override public Optional getUser(@NotNull UUID uuid) { try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - SELECT "uuid", "username" - FROM "%users_table%" - WHERE "uuid"=?"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "SELECT \"uuid\", \"username\" " + + "FROM \"%users_table%\" " + + "WHERE \"uuid\"=?" + ))) { statement.setObject(1, uuid); @@ -199,10 +201,11 @@ public class PostgresDatabase extends Database { @Override public Optional getUserByName(@NotNull String username) { try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - SELECT "uuid", "username" - FROM "%users_table%" - WHERE "username"=?"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "SELECT \"uuid\", \"username\" " + + "FROM \"%users_table%\" " + + "WHERE \"username\"=?" + ))) { statement.setString(1, username); final ResultSet resultSet = statement.executeQuery(); @@ -221,12 +224,13 @@ public class PostgresDatabase extends Database { @Override public Optional getLatestSnapshot(@NotNull User user) { try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - SELECT "version_uuid", "timestamp", "data" - FROM "%user_data_table%" - WHERE "player_uuid"=? - ORDER BY "timestamp" DESC - LIMIT 1;"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "SELECT \"version_uuid\", \"timestamp\", \"data\" " + + "FROM \"%user_data_table%\" " + + "WHERE \"player_uuid\"=? " + + "ORDER BY \"timestamp\" DESC " + + "LIMIT 1;" + ))) { statement.setObject(1, user.getUuid()); final ResultSet resultSet = statement.executeQuery(); if (resultSet.next()) { @@ -250,11 +254,12 @@ public class PostgresDatabase extends Database { public List getAllSnapshots(@NotNull User user) { final List retrievedData = Lists.newArrayList(); try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - SELECT "version_uuid", "timestamp", "data" - FROM "%user_data_table%" - WHERE "player_uuid"=? - ORDER BY "timestamp" DESC;"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "SELECT \"version_uuid\", \"timestamp\", \"data\" " + + "FROM \"%user_data_table%\" " + + "WHERE \"player_uuid\"=? " + + "ORDER BY \"timestamp\" DESC;" + ))) { statement.setObject(1, user.getUuid()); final ResultSet resultSet = statement.executeQuery(); while (resultSet.next()) { @@ -277,12 +282,13 @@ public class PostgresDatabase extends Database { @Override public Optional getSnapshot(@NotNull User user, @NotNull UUID versionUuid) { try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - SELECT "version_uuid", "timestamp", "data" - FROM "%user_data_table%" - WHERE "player_uuid"=? AND "version_uuid"=? - ORDER BY "timestamp" DESC - LIMIT 1;"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "SELECT \"version_uuid\", \"timestamp\", \"data\" " + + "FROM \"%user_data_table%\" " + + "WHERE \"player_uuid\"=? AND \"version_uuid\"=? " + + "ORDER BY \"timestamp\" DESC " + + "LIMIT 1;" + ))) { statement.setObject(1, user.getUuid()); statement.setObject(2, versionUuid); final ResultSet resultSet = statement.executeQuery(); @@ -304,17 +310,18 @@ public class PostgresDatabase extends Database { @Override protected void rotateSnapshots(@NotNull User user) { final List unpinnedUserData = getAllSnapshots(user).stream() - .filter(dataSnapshot -> !dataSnapshot.isPinned()).toList(); + .filter(dataSnapshot -> !dataSnapshot.isPinned()) + .collect(Collectors.toList()); final int maxSnapshots = plugin.getSettings().getSynchronization().getMaxUserDataSnapshots(); if (unpinnedUserData.size() > maxSnapshots) { try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - DELETE FROM "%user_data_table%" - WHERE "player_uuid"=? - AND "pinned" = FALSE - ORDER BY "timestamp" ASC - LIMIT %entry_count%;""".replace("%entry_count%", - Integer.toString(unpinnedUserData.size() - maxSnapshots))))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "DELETE FROM \"%user_data_table%\"\n" + + "WHERE \"player_uuid\"=?\n" + + "AND \"pinned\" = FALSE\n" + + "ORDER BY \"timestamp\" ASC\n" + + "LIMIT " + (unpinnedUserData.size() - maxSnapshots) + ";" + ))) { statement.setObject(1, user.getUuid()); statement.executeUpdate(); } @@ -328,10 +335,11 @@ public class PostgresDatabase extends Database { @Override public boolean deleteSnapshot(@NotNull User user, @NotNull UUID versionUuid) { try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - DELETE FROM "%user_data_table%" - WHERE "player_uuid"=? AND "version_uuid"=? - LIMIT 1;"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "DELETE FROM \"%user_data_table%\" " + + "WHERE \"player_uuid\"=? AND \"version_uuid\"=? " + + "LIMIT 1;" + ))) { statement.setObject(1, user.getUuid()); statement.setString(2, versionUuid.toString()); return statement.executeUpdate() > 0; @@ -346,15 +354,16 @@ public class PostgresDatabase extends Database { @Override protected void rotateLatestSnapshot(@NotNull User user, @NotNull OffsetDateTime within) { try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - DELETE FROM "%user_data_table%" - WHERE "player_uuid"=? AND "timestamp" = ( - SELECT "timestamp" - FROM "%user_data_table%" - WHERE "player_uuid"=? AND "timestamp" > ? AND "pinned" = FALSE - ORDER BY "timestamp" ASC - LIMIT 1 - );"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "DELETE FROM \"%user_data_table%\" " + + "WHERE \"player_uuid\"=? AND \"timestamp\" = ( " + + " SELECT \"timestamp\" " + + " FROM \"%user_data_table%\" " + + " WHERE \"player_uuid\"=? AND \"timestamp\" > ? AND \"pinned\" = FALSE " + + " ORDER BY \"timestamp\" ASC " + + " LIMIT 1 " + + ");" + ))) { statement.setObject(1, user.getUuid()); statement.setObject(2, user.getUuid()); statement.setTimestamp(3, Timestamp.from(within.toInstant())); @@ -369,10 +378,11 @@ public class PostgresDatabase extends Database { @Override protected void createSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) { try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - INSERT INTO "%user_data_table%" - ("player_uuid","version_uuid","timestamp","save_cause","pinned","data") - VALUES (?,?,?,?,?,?);"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "INSERT INTO \"%user_data_table%\" " + + "(\"player_uuid\",\"version_uuid\",\"timestamp\",\"save_cause\",\"pinned\",\"data\") " + + "VALUES (?,?,?,?,?,?);" + ))) { statement.setObject(1, user.getUuid()); statement.setObject(2, data.getId()); statement.setTimestamp(3, Timestamp.from(data.getTimestamp().toInstant())); @@ -390,11 +400,12 @@ public class PostgresDatabase extends Database { @Override public void updateSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) { try (Connection connection = getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(formatStatementTables(""" - UPDATE "%user_data_table%" - SET "save_cause"=?,"pinned"=?,"data"=? - WHERE "player_uuid"=? AND "version_uuid"=? - LIMIT 1;"""))) { + try (PreparedStatement statement = connection.prepareStatement(formatStatementTables( + "UPDATE \"%user_data_table%\" " + + "SET \"save_cause\"=?,\"pinned\"=?,\"data\"=? " + + "WHERE \"player_uuid\"=? AND \"version_uuid\"=? " + + "LIMIT 1;" + ))) { statement.setString(1, data.getSaveCause().name()); statement.setBoolean(2, data.isPinned()); statement.setBytes(3, data.asBytes(plugin)); diff --git a/common/src/main/java/net/william278/husksync/hook/PlanHook.java b/common/src/main/java/net/william278/husksync/hook/PlanHook.java index 7ffd7fb2..3d35116c 100644 --- a/common/src/main/java/net/william278/husksync/hook/PlanHook.java +++ b/common/src/main/java/net/william278/husksync/hook/PlanHook.java @@ -34,6 +34,7 @@ import net.william278.husksync.data.DataSnapshot; import org.jetbrains.annotations.NotNull; import java.time.OffsetDateTime; +import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.logging.Level; @@ -248,7 +249,8 @@ public class PlanHook { return getLatestSnapshot(playerUUID) .flatMap(DataHolder::getAdvancements) .map(Data.Advancements::getCompleted) - .stream().count(); + .map(List::size) + .orElse(0); } @Conditional("hasSynced") diff --git a/common/src/main/java/net/william278/husksync/listener/EventListener.java b/common/src/main/java/net/william278/husksync/listener/EventListener.java index 3cd22d3d..6b31a412 100644 --- a/common/src/main/java/net/william278/husksync/listener/EventListener.java +++ b/common/src/main/java/net/william278/husksync/listener/EventListener.java @@ -19,6 +19,8 @@ package net.william278.husksync.listener; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; import net.william278.husksync.HuskSync; import net.william278.husksync.data.Data; import net.william278.husksync.data.DataSnapshot; @@ -159,13 +161,13 @@ public abstract class EventListener { @NotNull private Map.Entry toEntry() { - return Map.entry(name().toLowerCase(), defaultPriority.name()); + return Maps.immutableEntry(name().toLowerCase(), defaultPriority.name()); } @SuppressWarnings("unchecked") @NotNull public static Map getDefaults() { - return Map.ofEntries(Arrays.stream(values()) + return ImmutableMap.ofEntries(Arrays.stream(values()) .map(ListenerType::toEntry) .toArray(Map.Entry[]::new)); } diff --git a/common/src/main/java/net/william278/husksync/migrator/Migrator.java b/common/src/main/java/net/william278/husksync/migrator/Migrator.java index c9389055..619d4ed6 100644 --- a/common/src/main/java/net/william278/husksync/migrator/Migrator.java +++ b/common/src/main/java/net/william278/husksync/migrator/Migrator.java @@ -19,6 +19,7 @@ package net.william278.husksync.migrator; +import com.google.common.base.Strings; import net.william278.husksync.HuskSync; import org.jetbrains.annotations.NotNull; @@ -56,7 +57,9 @@ public abstract class Migrator { * @return The data string obfuscated with stars (*) */ protected final String obfuscateDataString(@NotNull String dataString) { - return (dataString.length() > 1 ? dataString.charAt(0) + "*".repeat(dataString.length() - 1) : ""); + return (dataString.length() > 1 + ? dataString.charAt(0) + Strings.repeat("*", dataString.length() - 1) + : ""); } @NotNull diff --git a/common/src/main/java/net/william278/husksync/redis/RedisManager.java b/common/src/main/java/net/william278/husksync/redis/RedisManager.java index 4ffa324e..c6b90ae8 100644 --- a/common/src/main/java/net/william278/husksync/redis/RedisManager.java +++ b/common/src/main/java/net/william278/husksync/redis/RedisManager.java @@ -23,6 +23,7 @@ import net.william278.husksync.HuskSync; import net.william278.husksync.config.Settings; import net.william278.husksync.data.DataSnapshot; import net.william278.husksync.user.User; +import net.william278.husksync.util.CompletableFutureUtil; import org.jetbrains.annotations.Blocking; import org.jetbrains.annotations.NotNull; import redis.clients.jedis.*; @@ -127,8 +128,7 @@ public class RedisManager extends JedisPubSub { } if (reconnected) { - plugin.log(Level.WARNING, "Redis Server connection lost. Attempting reconnect in %ss..." - .formatted(RECONNECTION_TIME / 1000), t); + plugin.log(Level.WARNING, "Redis Server connection lost. Attempting reconnect in " + (RECONNECTION_TIME / 1000) + "s...", t); } try { this.unsubscribe(); @@ -157,7 +157,8 @@ public class RedisManager extends JedisPubSub { final RedisMessage redisMessage = RedisMessage.fromJson(plugin, message); switch (messageType) { - case UPDATE_USER_DATA -> plugin.getOnlineUser(redisMessage.getTargetUuid()).ifPresent( + case UPDATE_USER_DATA: { + plugin.getOnlineUser(redisMessage.getTargetUuid()).ifPresent( user -> { try { final DataSnapshot.Packed data = DataSnapshot.deserialize(plugin, redisMessage.getPayload()); @@ -167,14 +168,19 @@ public class RedisManager extends JedisPubSub { user.completeSync(false, DataSnapshot.UpdateCause.UPDATED, plugin); } } - ); - case REQUEST_USER_DATA -> plugin.getOnlineUser(redisMessage.getTargetUuid()).ifPresent( + ); + break; + } + case REQUEST_USER_DATA: { + plugin.getOnlineUser(redisMessage.getTargetUuid()).ifPresent( user -> RedisMessage.create( - UUID.fromString(new String(redisMessage.getPayload(), StandardCharsets.UTF_8)), - user.createSnapshot(DataSnapshot.SaveCause.INVENTORY_COMMAND).asBytes(plugin) + UUID.fromString(new String(redisMessage.getPayload(), StandardCharsets.UTF_8)), + user.createSnapshot(DataSnapshot.SaveCause.INVENTORY_COMMAND).asBytes(plugin) ).dispatch(plugin, RedisMessage.Type.RETURN_USER_DATA) - ); - case RETURN_USER_DATA -> { + ); + break; + } + case RETURN_USER_DATA: { final CompletableFuture> future = pendingRequests.get( redisMessage.getTargetUuid() ); @@ -188,6 +194,10 @@ public class RedisManager extends JedisPubSub { } pendingRequests.remove(redisMessage.getTargetUuid()); } + break; + } + default: { + // Do nothing } } } @@ -234,8 +244,7 @@ public class RedisManager extends JedisPubSub { ); redisMessage.dispatch(plugin, RedisMessage.Type.REQUEST_USER_DATA); }); - return future - .orTimeout( + return CompletableFutureUtil.orTimeout(future, plugin.getSettings().getSynchronization().getNetworkLatencyMilliseconds(), TimeUnit.MILLISECONDS ) diff --git a/common/src/main/java/net/william278/husksync/sync/DataSyncer.java b/common/src/main/java/net/william278/husksync/sync/DataSyncer.java index 749f5c41..f171907f 100644 --- a/common/src/main/java/net/william278/husksync/sync/DataSyncer.java +++ b/common/src/main/java/net/william278/husksync/sync/DataSyncer.java @@ -26,6 +26,7 @@ import net.william278.husksync.database.Database; import net.william278.husksync.redis.RedisManager; import net.william278.husksync.user.OnlineUser; import net.william278.husksync.user.User; +import net.william278.husksync.util.OptionalUtil; import net.william278.husksync.util.Task; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Blocking; @@ -158,12 +159,12 @@ public abstract class DataSyncer { @ApiStatus.Internal protected void setUserFromDatabase(@NotNull OnlineUser user) { try { - getDatabase().getLatestSnapshot(user).ifPresentOrElse( + OptionalUtil.ifPresentOrElse(getDatabase().getLatestSnapshot(user), snapshot -> user.applySnapshot(snapshot, DataSnapshot.UpdateCause.SYNCHRONIZED), () -> user.completeSync(true, DataSnapshot.UpdateCause.NEW_USER, plugin) ); } catch (Throwable e) { - plugin.log(Level.WARNING, "Failed to set %s's data from the database".formatted(user.getUsername()), e); + plugin.log(Level.WARNING, "Failed to set " + user.getUsername() + "'s data from the database", e); user.completeSync(false, DataSnapshot.UpdateCause.SYNCHRONIZED, plugin); } } diff --git a/common/src/main/java/net/william278/husksync/sync/LockstepDataSyncer.java b/common/src/main/java/net/william278/husksync/sync/LockstepDataSyncer.java index ad2329c8..66c2e1af 100644 --- a/common/src/main/java/net/william278/husksync/sync/LockstepDataSyncer.java +++ b/common/src/main/java/net/william278/husksync/sync/LockstepDataSyncer.java @@ -23,6 +23,7 @@ import net.william278.husksync.HuskSync; import net.william278.husksync.data.DataSnapshot; import net.william278.husksync.redis.RedisKeyType; import net.william278.husksync.user.OnlineUser; +import net.william278.husksync.util.OptionalUtil; import org.jetbrains.annotations.NotNull; public class LockstepDataSyncer extends DataSyncer { @@ -49,7 +50,7 @@ public class LockstepDataSyncer extends DataSyncer { return false; } getRedis().setUserCheckedOut(user, true); - getRedis().getUserData(user).ifPresentOrElse( + OptionalUtil.ifPresentOrElse(getRedis().getUserData(user), data -> user.applySnapshot(data, DataSnapshot.UpdateCause.SYNCHRONIZED), () -> this.setUserFromDatabase(user) ); diff --git a/common/src/main/java/net/william278/husksync/user/OnlineUser.java b/common/src/main/java/net/william278/husksync/user/OnlineUser.java index c64a4881..b7af1bf7 100644 --- a/common/src/main/java/net/william278/husksync/user/OnlineUser.java +++ b/common/src/main/java/net/william278/husksync/user/OnlineUser.java @@ -89,9 +89,9 @@ public abstract class OnlineUser extends User implements CommandUser, UserDataHo * @param description the description of the toast * @param iconMaterial the namespace-keyed material to use as an hasIcon of the toast * @param backgroundType the background ("ToastType") of the toast - * @deprecated No longer supported + * @deprecated No longer supported, since 3.6.7 */ - @Deprecated(since = "3.6.7") + @Deprecated public abstract void sendToast(@NotNull MineDown title, @NotNull MineDown description, @NotNull String iconMaterial, @NotNull String backgroundType); @@ -145,8 +145,17 @@ public abstract class OnlineUser extends User implements CommandUser, UserDataHo public void completeSync(boolean succeeded, @NotNull DataSnapshot.UpdateCause cause, @NotNull HuskSync plugin) { if (succeeded) { switch (plugin.getSettings().getSynchronization().getNotificationDisplaySlot()) { - case CHAT -> cause.getCompletedLocale(plugin).ifPresent(this::sendMessage); - case ACTION_BAR -> cause.getCompletedLocale(plugin).ifPresent(this::sendActionBar); + case CHAT: { + cause.getCompletedLocale(plugin).ifPresent(this::sendMessage); + break; + } + case ACTION_BAR:{ + cause.getCompletedLocale(plugin).ifPresent(this::sendActionBar); + break; + } + default: { + // do nothing + } } plugin.fireEvent( plugin.getSyncCompleteEvent(this), diff --git a/common/src/main/java/net/william278/husksync/user/User.java b/common/src/main/java/net/william278/husksync/user/User.java index 19c4bbc1..6f65b0d7 100644 --- a/common/src/main/java/net/william278/husksync/user/User.java +++ b/common/src/main/java/net/william278/husksync/user/User.java @@ -55,7 +55,8 @@ public class User { @Override public boolean equals(Object object) { - if (object instanceof User other) { + if (object instanceof User) { + User other = (User) object; return this.getUuid().equals(other.getUuid()); } return super.equals(object); diff --git a/common/src/main/java/net/william278/husksync/util/CompletableFutureUtil.java b/common/src/main/java/net/william278/husksync/util/CompletableFutureUtil.java new file mode 100644 index 00000000..201f48e6 --- /dev/null +++ b/common/src/main/java/net/william278/husksync/util/CompletableFutureUtil.java @@ -0,0 +1,86 @@ +/* + * This file is part of HuskSync, licensed under the Apache License 2.0. + * + * Copyright (c) William278 + * Copyright (c) contributors + * + * 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 net.william278.husksync.util; + +import lombok.experimental.UtilityClass; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.BiConsumer; + +@UtilityClass +public class CompletableFutureUtil { + public static CompletableFuture orTimeout(CompletableFuture $this, long timeout, TimeUnit unit) { + if (unit == null) + throw new NullPointerException(); + if (!$this.isDone()) { + $this.whenComplete(new Canceller(Delayer.delay(new Timeout($this), + timeout, unit))); + } + return $this; + } + + static final class Canceller implements BiConsumer { + final Future f; + Canceller(Future f) { this.f = f; } + public void accept(Object ignore, Throwable ex) { + if (ex == null && f != null && !f.isDone()) + f.cancel(false); + } + } + + static final class Delayer { + static ScheduledFuture delay(Runnable command, long delay, + TimeUnit unit) { + return delayer.schedule(command, delay, unit); + } + + static final class DaemonThreadFactory implements ThreadFactory { + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setDaemon(true); + t.setName("CompletableFutureDelayScheduler"); + return t; + } + } + + static final ScheduledThreadPoolExecutor delayer; + static { + (delayer = new ScheduledThreadPoolExecutor( + 1, new Delayer.DaemonThreadFactory())). + setRemoveOnCancelPolicy(true); + } + } + + static final class Timeout implements Runnable { + final CompletableFuture f; + Timeout(CompletableFuture f) { this.f = f; } + public void run() { + if (f != null && !f.isDone()) + f.completeExceptionally(new TimeoutException()); + } + } +} + diff --git a/common/src/main/java/net/william278/husksync/util/DataDumper.java b/common/src/main/java/net/william278/husksync/util/DataDumper.java index 8b4a1fe1..e3bdb8c5 100644 --- a/common/src/main/java/net/william278/husksync/util/DataDumper.java +++ b/common/src/main/java/net/william278/husksync/util/DataDumper.java @@ -21,6 +21,7 @@ package net.william278.husksync.util; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import lombok.SneakyThrows; import net.william278.husksync.HuskSync; import net.william278.husksync.data.DataSnapshot; import net.william278.husksync.user.User; @@ -124,8 +125,9 @@ public class DataDumper { } @NotNull + @SneakyThrows private String getWebContentField() { - return "content=" + URLEncoder.encode(toString(), StandardCharsets.UTF_8); + return "content=" + URLEncoder.encode(toString(), "UTF-8"); } /** @@ -136,7 +138,7 @@ public class DataDumper { @NotNull public String toFile() throws IOException { final Path filePath = getFilePath(); - try (final FileWriter writer = new FileWriter(filePath.toFile(), StandardCharsets.UTF_8, false)) { + try (final Writer writer = Files.newBufferedWriter(filePath, StandardCharsets.UTF_8)) { writer.write(toString()); // Write the data from #getString to the file using a writer return filePath.toString(); } catch (IOException e) { diff --git a/common/src/main/java/net/william278/husksync/util/DataSnapshotList.java b/common/src/main/java/net/william278/husksync/util/DataSnapshotList.java index 9b2c8eb0..4f87b203 100644 --- a/common/src/main/java/net/william278/husksync/util/DataSnapshotList.java +++ b/common/src/main/java/net/william278/husksync/util/DataSnapshotList.java @@ -30,6 +30,7 @@ import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; /** * Represents a chat-viewable paginated list of {@link net.william278.husksync.data.DataSnapshot}s @@ -60,7 +61,8 @@ public class DataSnapshotList { snapshot.getSaveCause().getLocale(plugin), String.format("%.2fKiB", snapshot.getFileSize(plugin) / 1024f), snapshot.isInvalid() ? snapshot.getInvalidReason(plugin) : "") - .orElse("• " + snapshot.getId())).toList(), + .orElse("• " + snapshot.getId())) + .collect(Collectors.toList()), plugin.getLocales().getBaseChatList(6) .setHeaderFormat(plugin.getLocales() .getRawLocale("data_list_title", dataOwner.getUsername(), diff --git a/common/src/main/java/net/william278/husksync/util/InputStreamUtil.java b/common/src/main/java/net/william278/husksync/util/InputStreamUtil.java new file mode 100644 index 00000000..6731bd90 --- /dev/null +++ b/common/src/main/java/net/william278/husksync/util/InputStreamUtil.java @@ -0,0 +1,108 @@ +/* + * This file is part of HuskSync, licensed under the Apache License 2.0. + * + * Copyright (c) William278 + * Copyright (c) contributors + * + * 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 net.william278.husksync.util; + +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@UtilityClass +public class InputStreamUtil { + // MAX_SKIP_BUFFER_SIZE is used to determine the maximum buffer size to + // use when skipping. + private static final int MAX_SKIP_BUFFER_SIZE = 2048; + + private static final int DEFAULT_BUFFER_SIZE = 8192; + + private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8; + + public static byte[] readAllBytes(InputStream $this) throws IOException { + return readNBytes($this, Integer.MAX_VALUE); + } + + public static byte[] readNBytes(InputStream $this, int len) throws IOException { + if (len < 0) { + throw new IllegalArgumentException("len < 0"); + } + + List bufs = null; + byte[] result = null; + int total = 0; + int remaining = len; + int n; + do { + byte[] buf = new byte[Math.min(remaining, DEFAULT_BUFFER_SIZE)]; + int nread = 0; + + // read to EOF which may read more or less than buffer size + while ((n = $this.read(buf, nread, + Math.min(buf.length - nread, remaining))) > 0) { + nread += n; + remaining -= n; + } + + if (nread > 0) { + if (MAX_BUFFER_SIZE - total < nread) { + throw new OutOfMemoryError("Required array size too large"); + } + if (nread < buf.length) { + buf = Arrays.copyOfRange(buf, 0, nread); + } + total += nread; + if (result == null) { + result = buf; + } else { + if (bufs == null) { + bufs = new ArrayList<>(); + bufs.add(result); + } + bufs.add(buf); + } + } + // if the last call to read returned -1 or the number of bytes + // requested have been read then break + } while (n >= 0 && remaining > 0); + + if (bufs == null) { + if (result == null) { + return new byte[0]; + } + return result.length == total ? + result : Arrays.copyOf(result, total); + } + + result = new byte[total]; + int offset = 0; + remaining = total; + for (byte[] b : bufs) { + int count = Math.min(b.length, remaining); + System.arraycopy(b, 0, result, offset, count); + offset += count; + remaining -= count; + } + + return result; + } +} diff --git a/common/src/main/java/net/william278/husksync/util/OptionalUtil.java b/common/src/main/java/net/william278/husksync/util/OptionalUtil.java new file mode 100644 index 00000000..421cc512 --- /dev/null +++ b/common/src/main/java/net/william278/husksync/util/OptionalUtil.java @@ -0,0 +1,54 @@ +/* + * This file is part of HuskSync, licensed under the Apache License 2.0. + * + * Copyright (c) William278 + * Copyright (c) contributors + * + * 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 net.william278.husksync.util; + +import lombok.experimental.UtilityClass; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Stream; + +@UtilityClass +public class OptionalUtil { + public static Optional or(Optional $this, Supplier> supplier) { + Objects.requireNonNull(supplier); + if ($this.isPresent()) { + return $this; + } else { + @SuppressWarnings("unchecked") + Optional r = (Optional) supplier.get(); + return Objects.requireNonNull(r); + } + } + + public static Stream stream(Optional $this) { + return $this.map(Stream::of).orElseGet(Stream::empty); + } + + public static void ifPresentOrElse(Optional $this, Consumer action, Runnable emptyAction) { + if ($this.isPresent()) { + action.accept($this.get()); + } else { + emptyAction.run(); + } + } +} diff --git a/common/src/main/java/net/william278/husksync/util/StringUtil.java b/common/src/main/java/net/william278/husksync/util/StringUtil.java new file mode 100644 index 00000000..05f83531 --- /dev/null +++ b/common/src/main/java/net/william278/husksync/util/StringUtil.java @@ -0,0 +1,38 @@ +/* + * This file is part of HuskSync, licensed under the Apache License 2.0. + * + * Copyright (c) William278 + * Copyright (c) contributors + * + * 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 net.william278.husksync.util; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public class StringUtil { + public static boolean isBlank(String $this) { + final int strLen = $this.length(); + if (strLen == 0) { + return true; + } + for (int i = 0; i < strLen; i++) { + if (!Character.isWhitespace($this.charAt(i))) { + return false; + } + } + return true; + } +} diff --git a/paper/build.gradle b/paper/build.gradle index 23846e49..8f1b4a9d 100644 --- a/paper/build.gradle +++ b/paper/build.gradle @@ -16,10 +16,16 @@ dependencies { } shadowJar { + mergeServiceFiles() + dependencies { exclude(dependency('com.mojang:brigadier')) } + relocate 'org.inksnow.cputil', 'net.william278.husksync.libraries.cputil' + relocate 'org.slf4j', 'net.william278.husksync.libraries.slf4j' + relocate 'org.objectweb.asm', 'net.william278.husksync.libraries.asm' + relocate 'org.apache.commons.io', 'net.william278.husksync.libraries.commons.io' relocate 'org.apache.commons.text', 'net.william278.husksync.libraries.commons.text' relocate 'org.apache.commons.lang3', 'net.william278.husksync.libraries.commons.lang3' @@ -43,8 +49,6 @@ shadowJar { relocate 'dev.triumphteam.gui', 'net.william278.husksync.libraries.triumphgui' relocate 'space.arim.morepaperlib', 'net.william278.husksync.libraries.paperlib' relocate 'de.tr7zw.changeme.nbtapi', 'net.william278.husksync.libraries.nbtapi' - - minimize() } tasks { diff --git a/paper/src/main/java/net/william278/husksync/PaperHuskSyncLoader.java b/paper/src/main/java/net/william278/husksync/PaperHuskSyncLoader.java index 9637453c..a78cbf89 100644 --- a/paper/src/main/java/net/william278/husksync/PaperHuskSyncLoader.java +++ b/paper/src/main/java/net/william278/husksync/PaperHuskSyncLoader.java @@ -32,6 +32,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.InputStream; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -63,7 +64,7 @@ public class PaperHuskSyncLoader implements PluginLoader { } catch (Throwable e) { classpathBuilder.getContext().getLogger().error("Failed to resolve libraries", e); } - return List.of(); + return Collections.emptyList(); } @Nullable diff --git a/paper/src/main/java/net/william278/husksync/listener/PaperEventListener.java b/paper/src/main/java/net/william278/husksync/listener/PaperEventListener.java index d3b988d9..4881408c 100644 --- a/paper/src/main/java/net/william278/husksync/listener/PaperEventListener.java +++ b/paper/src/main/java/net/william278/husksync/listener/PaperEventListener.java @@ -61,10 +61,18 @@ public class PaperEventListener extends BukkitEventListener { // Paper - support saving the player's items to keep if enabled final int maxInventorySize = BukkitData.Items.Inventory.INVENTORY_SLOT_COUNT; - final List itemsToSave = switch (settings.getItemsToSave()) { - case DROPS -> event.getDrops(); - case ITEMS_TO_KEEP -> preserveOrder(event.getEntity().getInventory(), event.getItemsToKeep()); - }; + final List itemsToSave; + switch (settings.getItemsToSave()) { + case DROPS:{ + itemsToSave = event.getDrops(); + break; + } + case ITEMS_TO_KEEP: { + itemsToSave = preserveOrder(event.getEntity().getInventory(), event.getItemsToKeep()); + break; + } + default: throw new IllegalStateException("Unexpected value: " + settings.getItemsToSave()); + } if (itemsToSave.size() > maxInventorySize) { itemsToSave.subList(maxInventorySize, itemsToSave.size()).clear(); } diff --git a/settings.gradle b/settings.gradle index fefb6984..eb6d9100 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,5 +10,5 @@ include( 'common', 'bukkit', 'paper', - 'fabric' +// 'fabric' ) \ No newline at end of file