diff --git a/api/src/main/java/com/discordsrv/api/component/MinecraftComponent.java b/api/src/main/java/com/discordsrv/api/component/MinecraftComponent.java index bd79cea1..86db7345 100644 --- a/api/src/main/java/com/discordsrv/api/component/MinecraftComponent.java +++ b/api/src/main/java/com/discordsrv/api/component/MinecraftComponent.java @@ -32,7 +32,7 @@ import java.util.Optional; /** * A Minecraft json text component. Use {@link DiscordSRVApi#componentFactory()} to get an instance.
*
- * This is designed to work with Adventure, see {@link #adventureAdapter(Class)} & {@link #adventureAdapter(MinecraftComponentAdapter)} + * This is designed to work with Adventure, see {@link #adventureAdapter(Class, Class)} & {@link #adventureAdapter(MinecraftComponentAdapter)} * but is compatible with anything able to handle Minecraft's json format. * Legacy is not supported. */ @@ -66,11 +66,26 @@ public interface MinecraftComponent { * Creates an Adventure adapter for convenience. * * @param gsonSerializerClass the gson serializer class - * @return a adapter that will convert to/from relocated or unrelocated adventure classes to/from json + * @return an adapter that will convert to/from relocated or unrelocated adventure classes to/from json * @throws IllegalArgumentException if the provided class is not an Adventure GsonComponentSerializer + * @see #adventureAdapter(Class, Class) */ @NotNull - Adapter adventureAdapter(@NotNull Class gsonSerializerClass); + default Adapter adventureAdapter(@NotNull Class gsonSerializerClass) { + return adventureAdapter(gsonSerializerClass, null); + } + + /** + * Creates an Adventure adapter for convenience. + * + * @param gsonSerializerClass the {@code GsonComponentSerializer} class + * @param componentClass the {@code Component} class that's returned by the given gson component serializer + * @return an adapter that will convert to/from relocated or unrelocated adventure classes to/from json + * @throws IllegalArgumentException if the provided class is not an Adventure {@code GsonComponentSerializer} + * or if the provided {@code Component} class isn't the one returned by the serializer + */ + @NotNull + Adapter adventureAdapter(@NotNull Class gsonSerializerClass, Class componentClass); /** * Creates an Adventure adapter from a {@link MinecraftComponentAdapter} for convenience. @@ -79,7 +94,7 @@ public interface MinecraftComponent { * @return a {@link Adapter} for this component using the given {@link MinecraftComponentAdapter} */ @NotNull - Adapter adventureAdapter(@NotNull MinecraftComponentAdapter adapter); + Adapter adventureAdapter(@NotNull MinecraftComponentAdapter adapter); /** * Creates an Adventure adapter for the unrelocated adventure. @@ -88,8 +103,8 @@ public interface MinecraftComponent { */ @NotNull @ApiStatus.NonExtendable - default Optional unrelocatedAdapter() { - MinecraftComponentAdapter adapter = MinecraftComponentAdapter.UNRELOCATED; + default Optional> unrelocatedAdapter() { + MinecraftComponentAdapter adapter = MinecraftComponentAdapter.UNRELOCATED; if (adapter == null) { return Optional.empty(); } @@ -99,21 +114,21 @@ public interface MinecraftComponent { /** * An Adventure adapter, converts from/to given adventure components from/to json. */ - interface Adapter { + interface Adapter { /** * Returns the Adventure Component returned by the gson serializer of this adapter. * @return the {@code net.kyori.adventure.text.Component} (or relocated), cast this to your end class */ @NotNull - Object getComponent(); + Component getComponent(); /** * Sets the component to the component that can be serialized by the gson serializer for this class. * @param adventureComponent the component * @throws IllegalArgumentException if the provided component cannot be processed by the gson serializer of this adapter */ - void setComponent(@NotNull Object adventureComponent); + void setComponent(@NotNull Component adventureComponent); } } diff --git a/api/src/main/java/com/discordsrv/api/component/MinecraftComponentAdapter.java b/api/src/main/java/com/discordsrv/api/component/MinecraftComponentAdapter.java index e28af54a..a646e619 100644 --- a/api/src/main/java/com/discordsrv/api/component/MinecraftComponentAdapter.java +++ b/api/src/main/java/com/discordsrv/api/component/MinecraftComponentAdapter.java @@ -30,12 +30,12 @@ import java.lang.reflect.Method; * A persistent Adventure adapter for {@link MinecraftComponent}s, this is more efficient than using {@link MinecraftComponent#adventureAdapter(Class)}. * @see MinecraftComponent#adventureAdapter(MinecraftComponentAdapter) */ -public class MinecraftComponentAdapter { +public class MinecraftComponentAdapter { - public static final MinecraftComponentAdapter UNRELOCATED; + public static final MinecraftComponentAdapter UNRELOCATED; static { - MinecraftComponentAdapter unrelocated = null; + MinecraftComponentAdapter unrelocated = null; try { unrelocated = MinecraftComponentAdapter.create( Class.forName("net.ky".concat("ori.adventure.text.serializer.gson.GsonComponentSerializer")) @@ -45,14 +45,26 @@ public class MinecraftComponentAdapter { } /** - * Creates a {@link MinecraftComponentAdapter} that can be used with {@link MinecraftComponent}s. + * Creates a new {@link MinecraftComponentAdapter} that can be used with {@link MinecraftComponent}s. * * @param gsonSerializerClass a GsonComponentSerializer class * @return a new {@link MinecraftComponentAdapter} with the provided GsonComponentSerializer * @throws IllegalArgumentException if the provided argument is not a GsonComponentSerialize class */ - public static MinecraftComponentAdapter create(Class gsonSerializerClass) { - return new MinecraftComponentAdapter(gsonSerializerClass); + public static MinecraftComponentAdapter create(Class gsonSerializerClass) { + return new MinecraftComponentAdapter<>(gsonSerializerClass, null); + } + + /** + * Creates a new {@link MinecraftComponentAdapter} that can be used with {@link MinecraftComponent}s. + * + * @param gsonSerializerClass a GsonComponentSerializer class + * @param componentClass the Component class returned by the GsonComponentSerializer + * @return a new {@link MinecraftComponentAdapter} with the provided GsonComponentSerializer + * @throws IllegalArgumentException if the provided argument is not a GsonComponentSerialize class + */ + public static MinecraftComponentAdapter create(Class gsonSerializerClass, Class componentClass) { + return new MinecraftComponentAdapter<>(gsonSerializerClass, componentClass); } private final Class gsonSerializerClass; @@ -60,31 +72,48 @@ public class MinecraftComponentAdapter { private final Method deserialize; private final Method serialize; - private MinecraftComponentAdapter(Class gsonSerializerClass) { + private MinecraftComponentAdapter(Class gsonSerializerClass, Class providedComponentClass) { try { this.gsonSerializerClass = gsonSerializerClass; this.instance = gsonSerializerClass.getDeclaredMethod("gson").invoke(null); this.deserialize = gsonSerializerClass.getMethod("deserialize", Object.class); Class componentClass = deserialize.getReturnType(); + checkComponentClass(providedComponentClass, componentClass); this.serialize = gsonSerializerClass.getMethod("serialize", componentClass); } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { throw new IllegalArgumentException("The provided class is not a GsonComponentSerializer", e); } } - public Class gsonSerializerClass() { + private static void checkComponentClass(Class provided, Class actual) { + if (provided == null) { + // Ignore null + return; + } + + String providedName = provided.getName(); + String actualName = actual.getName(); + if (!providedName.equals(actualName)) { + throw new IllegalArgumentException( + "The provided Component class (" + providedName + + ") does not match the one returned by the serializer: " + actualName + ); + } + } + + public Class serializerClass() { return gsonSerializerClass; } - public Object instance() { + public Object serializerInstance() { return instance; } - public Method deserialize() { + public Method deserializeMethod() { return deserialize; } - public Method serialize() { + public Method serializeMethod() { return serialize; } } diff --git a/buildscript/relocations.gradle b/buildscript/relocations.gradle index 48f977d0..ccacec80 100644 --- a/buildscript/relocations.gradle +++ b/buildscript/relocations.gradle @@ -31,8 +31,11 @@ shadowJar { // HikariCP 'com.zaxxer.hikari', - // Adventure, EnhancedLegacyText, MCDiscordReserializer - 'net.kyori', + // Adventure (API isn't relocated always) + 'net.kyori.adventure.platform', + 'net.kyori.adventure.text.serializer', + + // EnhancedLegacyText, MCDiscordReserializer 'dev.vankka.enhancedlegacytext', 'dev.vankka.mcdiscordreserializer', 'dev.vankka.simpleast', diff --git a/bukkit/build.gradle b/bukkit/build.gradle index 9df4dd1c..0b943e23 100644 --- a/bukkit/build.gradle +++ b/bukkit/build.gradle @@ -48,5 +48,6 @@ dependencies { shadowJar { archiveFileName = 'bukkit.jarinjar' - // Relocations in buildscript/relocations.gradle + relocate 'net.kyori', 'com.discordsrv.dependencies.net.kyori' + // More relocations in buildscript/relocations.gradle } diff --git a/bukkit/src/main/java/com/discordsrv/bukkit/component/util/PaperComponentUtil.java b/bukkit/src/main/java/com/discordsrv/bukkit/component/util/PaperComponentUtil.java index 16a15736..486fa10d 100644 --- a/bukkit/src/main/java/com/discordsrv/bukkit/component/util/PaperComponentUtil.java +++ b/bukkit/src/main/java/com/discordsrv/bukkit/component/util/PaperComponentUtil.java @@ -70,12 +70,10 @@ public final class PaperComponentUtil { } MinecraftComponent component = discordSRV.componentFactory().empty(); - MinecraftComponent.Adapter adapter = component.unrelocatedAdapter().orElse(null); - if (adapter == null) { - throw new IllegalStateException("Unrelocated adventure unavailable"); - } + component.unrelocatedAdapter() + .orElseThrow(() -> new IllegalStateException("Unrelocated adventure unavailable")) + .setComponent(unrelocated); - adapter.setComponent(unrelocated); return component; } } diff --git a/bungee/build.gradle b/bungee/build.gradle index e1c96e92..c03bb061 100644 --- a/bungee/build.gradle +++ b/bungee/build.gradle @@ -36,5 +36,6 @@ dependencies { shadowJar { archiveFileName = 'bungee.jarinjar' - // Relocations in buildscript/relocations.gradle + relocate 'net.kyori', 'com.discordsrv.dependencies.net.kyori' + // More relocations in buildscript/relocations.gradle } diff --git a/common/src/main/java/com/discordsrv/common/component/MinecraftComponentImpl.java b/common/src/main/java/com/discordsrv/common/component/MinecraftComponentImpl.java index 3a6c6674..9683fc2d 100644 --- a/common/src/main/java/com/discordsrv/common/component/MinecraftComponentImpl.java +++ b/common/src/main/java/com/discordsrv/common/component/MinecraftComponentImpl.java @@ -44,6 +44,15 @@ public class MinecraftComponentImpl implements MinecraftComponent { setComponent(component); } + public Component getComponent() { + return component; + } + + public void setComponent(Component component) { + this.component = component; + this.json = GsonComponentSerializer.gson().serialize(component); + } + @Override public @NotNull String asJson() { return json; @@ -66,43 +75,37 @@ public class MinecraftComponentImpl implements MinecraftComponent { return PlainTextComponentSerializer.plainText().serialize(component); } - public Component getComponent() { - return component; - } - - public void setComponent(Component component) { - this.component = component; - this.json = GsonComponentSerializer.gson().serialize(component); + @Override + public MinecraftComponent.@NotNull Adapter adventureAdapter( + @NotNull Class gsonSerializerClass, @NotNull Class componentClass + ) { + return new Adapter<>(gsonSerializerClass, componentClass); } @Override - public @NotNull MinecraftComponent.Adapter adventureAdapter(@NotNull Class gsonSerializerClass) { - return new Adapter(gsonSerializerClass); + public MinecraftComponent.@NotNull Adapter adventureAdapter(@NotNull MinecraftComponentAdapter adapter) { + return new Adapter<>(adapter); } - @Override - public @NotNull MinecraftComponent.Adapter adventureAdapter(@NotNull MinecraftComponentAdapter adapter) { - return new Adapter(adapter); - } + @SuppressWarnings("unchecked") + public class Adapter implements MinecraftComponent.Adapter { - public class Adapter implements MinecraftComponent.Adapter { + private final MinecraftComponentAdapter adapter; - private final MinecraftComponentAdapter adapter; - - private Adapter(Class gsonSerializerClass) { - this(MinecraftComponentAdapter.create(gsonSerializerClass)); + private Adapter(Class gsonSerializerClass, Class componentClass) { + this(MinecraftComponentAdapter.create(gsonSerializerClass, componentClass)); } - private Adapter(MinecraftComponentAdapter adapter) { + private Adapter(MinecraftComponentAdapter adapter) { this.adapter = adapter; } @Override - public @NotNull Object getComponent() { + public @NotNull T getComponent() { try { - return adapter.deserialize() + return (T) adapter.deserializeMethod() .invoke( - adapter.instance(), + adapter.serializerInstance(), json ); } catch (IllegalAccessException | InvocationTargetException e) { @@ -113,13 +116,15 @@ public class MinecraftComponentImpl implements MinecraftComponent { @Override public void setComponent(@NotNull Object adventureComponent) { try { - json = (String) adapter.serialize() - .invoke( - adapter.instance(), - adventureComponent - ); + setJson( + (String) adapter.serializeMethod() + .invoke( + adapter.serializerInstance(), + adventureComponent + ) + ); } catch (InvocationTargetException e) { - throw new IllegalArgumentException("The provided class is not a Component for the GsonComponentSerializer " + adapter.gsonSerializerClass().getName(), e); + throw new IllegalArgumentException("The provided class is not a Component for the GsonComponentSerializer " + adapter.serializerClass().getName(), e); } catch (IllegalAccessException e) { throw new RuntimeException("Failed to convert from adventure component", e); } diff --git a/common/src/main/java/com/discordsrv/common/component/util/ComponentUtil.java b/common/src/main/java/com/discordsrv/common/component/util/ComponentUtil.java index 525cc310..a5ebc3cb 100644 --- a/common/src/main/java/com/discordsrv/common/component/util/ComponentUtil.java +++ b/common/src/main/java/com/discordsrv/common/component/util/ComponentUtil.java @@ -34,7 +34,11 @@ import java.util.Collection; */ public final class ComponentUtil { - public static final MinecraftComponentAdapter ADAPTER = MinecraftComponentAdapter.create(GsonComponentSerializer.class); + private static MinecraftComponentAdapter ADAPTER; + + private static MinecraftComponentAdapter getAdapter() { + return ADAPTER != null ? ADAPTER : (ADAPTER = MinecraftComponentAdapter.create(GsonComponentSerializer.class, Component.class)); + } private ComponentUtil() {} @@ -43,7 +47,7 @@ public final class ComponentUtil { } public static boolean isEmpty(MinecraftComponent component) { - return isEmpty(fromAPI(component)); + return component.asPlainString().isEmpty(); } public static MinecraftComponent toAPI(Component component) { @@ -54,12 +58,16 @@ public final class ComponentUtil { if (component instanceof MinecraftComponentImpl) { return ((MinecraftComponentImpl) component).getComponent(); } else { - return (Component) component.adventureAdapter(ADAPTER).getComponent(); + return component.adventureAdapter(getAdapter()).getComponent(); } } public static void set(MinecraftComponent minecraftComponent, Component component) { - minecraftComponent.adventureAdapter(ADAPTER).setComponent(component); + if (component instanceof MinecraftComponentImpl) { + ((MinecraftComponentImpl) component).setComponent(component); + } else { + minecraftComponent.adventureAdapter(getAdapter()).setComponent(component); + } } public static Component fromUnrelocated(Object unrelocatedAdventure) { diff --git a/sponge/loader/src/main/java/com/discordsrv/sponge/loader/DiscordSRVSpongeLoader.java b/sponge/loader/src/main/java/com/discordsrv/sponge/loader/DiscordSRVSpongeLoader.java index fe55c0dd..ed49c1aa 100644 --- a/sponge/loader/src/main/java/com/discordsrv/sponge/loader/DiscordSRVSpongeLoader.java +++ b/sponge/loader/src/main/java/com/discordsrv/sponge/loader/DiscordSRVSpongeLoader.java @@ -24,6 +24,7 @@ import dev.vankka.mcdependencydownload.classloader.JarInJarClassLoader; import dev.vankka.mcdependencydownload.loader.ILoader; import dev.vankka.mcdependencydownload.loader.exception.LoadingException; import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; import org.spongepowered.api.Game; import org.spongepowered.api.config.ConfigDir; import org.spongepowered.api.event.Listener; @@ -34,10 +35,11 @@ import org.spongepowered.api.event.lifecycle.StoppingEngineEvent; import org.spongepowered.plugin.PluginContainer; import org.spongepowered.plugin.builtin.jvm.Plugin; +import java.io.IOException; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.nio.file.Path; +import java.util.Objects; import java.util.Optional; @Plugin("discordsrv") @@ -46,6 +48,7 @@ public class DiscordSRVSpongeLoader implements ILoader { private final PluginContainer pluginContainer; private final Game game; private final Path dataDirectory; + private final JarInJarClassLoader classLoader; private ISpongeBootstrap bootstrap; @Inject @@ -60,10 +63,11 @@ public class DiscordSRVSpongeLoader implements ILoader { logger.error("| DiscordSRV does not run on clients |"); logger.error("| DiscordSRV can only be installed on servers |"); logger.error("+---------------------------------------------+"); + this.classLoader = null; return; } - initialize(); + this.classLoader = initialize(); } private Optional bootstrap() { @@ -71,14 +75,12 @@ public class DiscordSRVSpongeLoader implements ILoader { } @Override - public String getBootstrapClassName() { + public @NotNull String getBootstrapClassName() { return "com.discordsrv.sponge.DiscordSRVSpongeBootstrap"; } @Override - public void initiateBootstrap(Class bootstrapClass, JarInJarClassLoader classLoader) - throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { - + public void initiateBootstrap(Class bootstrapClass, @NotNull JarInJarClassLoader classLoader) throws ReflectiveOperationException { Constructor constructor = bootstrapClass.getConstructor(PluginContainer.class, Game.class, JarInJarClassLoader.class, Path.class); bootstrap = (ISpongeBootstrap) constructor.newInstance(pluginContainer, game, classLoader, dataDirectory); } @@ -89,18 +91,18 @@ public class DiscordSRVSpongeLoader implements ILoader { } @Override - public String getName() { + public @NotNull String getName() { return "DiscordSRV"; } @Override - public ClassLoader getParentClassLoader() { + public @NotNull ClassLoader getParentClassLoader() { return getClass().getClassLoader(); } @Override - public URL getJarInJarResource() { - return getParentClassLoader().getResource("sponge.jarinjar"); + public @NotNull URL getJarInJarResource() { + return Objects.requireNonNull(getParentClassLoader().getResource("sponge.jarinjar")); } @Listener @@ -121,5 +123,10 @@ public class DiscordSRVSpongeLoader implements ILoader { @Listener public void onStoppingEngine(StoppingEngineEvent event) { bootstrap().ifPresent(ISpongeBootstrap::onStopping); + try { + classLoader.close(); + } catch (IOException e) { + pluginContainer.logger().error("Failed to close JarInJarClassLoader", e); + } } }