Make MinecraftComponent immutable

This commit is contained in:
Vankka 2024-07-26 23:38:56 +03:00
parent 295bc1c6bf
commit 6f8b5bd023
No known key found for this signature in database
GPG Key ID: 62E48025ED4E7EBB
18 changed files with 232 additions and 281 deletions

View File

@ -5,6 +5,9 @@ dependencies {
// Annotations
compileOnlyApi(libs.jetbrains.annotations)
// Unrelocate
compileOnly(project(':common:common-unrelocate'))
// JDA
api(libs.jda) {
// We don't use audio

View File

@ -43,7 +43,7 @@ import java.util.function.Predicate;
/**
* The DiscordSRV API.
*
* <p>
* Use your platform's service provider or {@link #get()} / {@link #optional()} to get the instance.
*/
@SuppressWarnings("unused") // API
@ -51,12 +51,17 @@ public interface DiscordSRVApi {
/**
* Gets the instance of {@link DiscordSRVApi}.
* @return the DiscordSRV api, or {@code null} if not available
* @return the DiscordSRV api
* @see #isAvailable()
* @throws IllegalStateException if DiscordSRV has not been initialized yet
*/
@Nullable
@NotNull
static DiscordSRVApi get() {
return InstanceHolder.API;
DiscordSRVApi api = InstanceHolder.API;
if (api == null) {
throw new IllegalStateException("DiscordSRV has not been initialized yet");
}
return api;
}
/**

View File

@ -24,6 +24,7 @@
package com.discordsrv.api.component;
import com.discordsrv.api.DiscordSRVApi;
import com.discordsrv.unrelocate.net.kyori.adventure.text.Component;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -31,7 +32,7 @@ import org.jetbrains.annotations.Nullable;
/**
* A Minecraft json text component. Use {@link DiscordSRVApi#componentFactory()} to get an instance.<br/>
* <br/>
* This is designed to work with Adventure, see {@link #adventureAdapter(Class, Class)} and {@link #adventureAdapter(MinecraftComponentAdapter)}
* This is designed to work with Adventure, see {@link #asAdventure(Class, Class)} and {@link #asAdventure(MinecraftComponentAdapter)}
* but is compatible with anything able to handle Minecraft's json format.
* Legacy is <b>not supported</b>.
*/
@ -39,6 +40,34 @@ import org.jetbrains.annotations.Nullable;
@ApiStatus.NonExtendable
public interface MinecraftComponent {
/**
* Creates a {@link MinecraftComponent} from the provided JSON.
*
* @param json the json
* @return a new {@link MinecraftComponent}
*/
@NotNull
static MinecraftComponent fromJson(@NotNull String json) {
return DiscordSRVApi.get()
.componentFactory()
.fromJson(json);
}
/**
* Creates a {@link MinecraftComponent} from the given unrelocated Adventure Component.
*
* @param unrelocatedAdventureComponent the <b>unrelocated</b> adventure Component
* @return a new {@link MinecraftComponent}
*/
@NotNull
static MinecraftComponent fromAdventure(@NotNull Component unrelocatedAdventureComponent) {
MinecraftComponentAdapter<Component> adapter = MinecraftComponentAdapter.unrelocated();
if (adapter == null) {
throw new IllegalStateException("Unrelocated Adventure GSON serializer not available");
}
return adapter.toDiscordSRV(unrelocatedAdventureComponent);
}
/**
* Gets this component as json.
* @return json of this component
@ -46,13 +75,6 @@ public interface MinecraftComponent {
@NotNull
String asJson();
/**
* Sets this component from json.
* @param json valid Minecraft message component json
* @throws IllegalArgumentException if the provided json is not valid
*/
void setJson(@NotNull String json);
/**
* Gets this message as a plain {@link String} losing any coloring, click and hover components it may have had.
* Use for reference only, <b>this is not a substitute for legacy</b>.
@ -62,72 +84,59 @@ public interface MinecraftComponent {
String asPlainString();
/**
* Creates an Adventure adapter for convenience.
* Converts this {@link MinecraftComponent} to an Adventure Component of the provided GSON serializer.
* Prefer using {@link #asAdventure(MinecraftComponentAdapter)}.
*
* @param gsonSerializerClass the gson serializer class
* @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)
* @see #asAdventure(Class, Class)
*/
@NotNull
default Adapter<Object> adventureAdapter(@NotNull Class<?> gsonSerializerClass) {
return adventureAdapter(gsonSerializerClass, null);
default Object asAdventure(@NotNull Class<?> gsonSerializerClass) {
return asAdventure(gsonSerializerClass, null);
}
/**
* Creates an Adventure adapter for convenience.
* Converts this {@link MinecraftComponent} to an Adventure Component of the provided GSON serializer.
* Prefer using {@link #asAdventure(MinecraftComponentAdapter)}.
*
* @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
* @return the component from the GSON serializers output
* @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
*/
@SuppressWarnings("unchecked")
@NotNull
<T> Adapter<T> adventureAdapter(@NotNull Class<?> gsonSerializerClass, Class<T> componentClass);
default <T> T asAdventure(@NotNull Class<?> gsonSerializerClass, Class<T> componentClass) {
return (T) MinecraftComponentAdapter.create(gsonSerializerClass).toAdventure(this);
}
/**
* Creates an Adventure adapter from a {@link MinecraftComponentAdapter} for convenience.
*
* @param adapter the pre-made {@link MinecraftComponentAdapter}
* @return a {@link Adapter} for this component using the given {@link MinecraftComponentAdapter}
* @return a new Adventure Component
*/
@NotNull
<T> Adapter<T> adventureAdapter(@NotNull MinecraftComponentAdapter<T> adapter);
default <T> T asAdventure(@NotNull MinecraftComponentAdapter<T> adapter) {
return adapter.toAdventure(this);
}
/**
* Creates an Adventure adapter for the unrelocated adventure.
*
* @return a {@link Adapter} for this component using the unrelocated adventure, {@code null} if not available
* @return the <b>unrelocated</b> Adventure Component, {@code null} if not available
*/
@Nullable
@ApiStatus.NonExtendable
default Adapter<Object> unrelocatedAdapter() {
MinecraftComponentAdapter<Object> adapter = MinecraftComponentAdapter.UNRELOCATED;
default Component asAdventure() {
MinecraftComponentAdapter<Component> adapter = MinecraftComponentAdapter.unrelocated();
if (adapter == null) {
return null;
}
return adventureAdapter(adapter);
}
/**
* An Adventure adapter, converts from/to given adventure components from/to json.
*/
interface Adapter<Component> {
/**
* 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
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 Component adventureComponent);
return asAdventure(adapter);
}
}

View File

@ -23,97 +23,66 @@
package com.discordsrv.api.component;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.discordsrv.api.DiscordSRVApi;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A persistent Adventure adapter for {@link MinecraftComponent}s, this is more efficient than using {@link MinecraftComponent#adventureAdapter(Class)}.
* @see MinecraftComponent#adventureAdapter(MinecraftComponentAdapter)
* A helper class to make {@link MinecraftComponent}s using Adventure.
* @param <Component> the Adventure Component type, relocated or unrelocated
*/
public class MinecraftComponentAdapter<Component> {
public interface MinecraftComponentAdapter<Component> {
public static final MinecraftComponentAdapter<Object> UNRELOCATED;
static {
MinecraftComponentAdapter<Object> unrelocated = null;
try {
unrelocated = MinecraftComponentAdapter.create(
Class.forName("net.ky".concat("ori.adventure.text.serializer.gson.GsonComponentSerializer"))
);
} catch (ClassNotFoundException ignored) {}
UNRELOCATED = unrelocated;
/**
* Create a new {@link MinecraftComponentAdapter} for the given GSONComponentSerializer class.
*
* @param gsonSerializerClass the serializer class
* @return a new {@link MinecraftComponentAdapter}
* @param <Component> the type of Adventure Component the serializer handles
*/
@NotNull
static <Component> MinecraftComponentAdapter<Component> create(Class<?> gsonSerializerClass) {
return create(gsonSerializerClass, null);
}
/**
* Creates a new {@link MinecraftComponentAdapter} that can be used with {@link MinecraftComponent}s.
* Create a new {@link MinecraftComponentAdapter} for the given GSONComponentSerializer class.
*
* @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
* @param gsonSerializerClass the serializer class
* @param componentClass the {@code Component} class that's returned by the given gson component serializer
* @return a new {@link MinecraftComponentAdapter}
* @param <T> the type of Adventure Component the serializer returns
* @throws IllegalArgumentException if the provided componentClass does not match the gsonSerializerClasses Component
*/
public static MinecraftComponentAdapter<Object> create(Class<?> gsonSerializerClass) {
return new MinecraftComponentAdapter<>(gsonSerializerClass, null);
@NotNull
static <T> MinecraftComponentAdapter<T> create(Class<?> gsonSerializerClass, Class<T> componentClass) {
return DiscordSRVApi.get()
.componentFactory()
.makeAdapter(gsonSerializerClass, componentClass);
}
/**
* Creates a new {@link MinecraftComponentAdapter} that can be used with {@link MinecraftComponent}s.
* Create a new {@link MinecraftComponentAdapter} for unrelocated Adventure.
*
* @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
* @return the shared instance of {@link MinecraftComponentAdapter} unrelocated Adventure, or {@code null}
*/
public static <Component> MinecraftComponentAdapter<Component> create(Class<?> gsonSerializerClass, Class<Component> componentClass) {
return new MinecraftComponentAdapter<>(gsonSerializerClass, componentClass);
@SuppressWarnings("unchecked")
@Nullable
static MinecraftComponentAdapter<com.discordsrv.unrelocate.net.kyori.adventure.text.Component> unrelocated() {
return (MinecraftComponentAdapter<com.discordsrv.unrelocate.net.kyori.adventure.text.Component>) (Object) MinecraftComponentAdapterUnrelocated.INSTANCE;
}
private final Class<?> gsonSerializerClass;
private final Object instance;
private final Method deserialize;
private final Method serialize;
/**
* Converts a {@link MinecraftComponent} to an Adventure Component.
* @param component the {@link MinecraftComponent}
* @return a new Adventure Component
*/
Component toAdventure(MinecraftComponent component);
private MinecraftComponentAdapter(Class<?> gsonSerializerClass, Class<Component> 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);
}
}
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 serializerInstance() {
return instance;
}
public Method deserializeMethod() {
return deserialize;
}
public Method serializeMethod() {
return serialize;
}
/**
* Converts an Adventure Component into a {@link MinecraftComponent}.
* @param component the Adventure Component
* @return a new {@link MinecraftComponent}
*/
MinecraftComponent toDiscordSRV(Component component);
}

View File

@ -25,6 +25,7 @@ package com.discordsrv.api.component;
import com.discordsrv.api.DiscordSRVApi;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A factory for creating {@link MinecraftComponent}s.
@ -33,12 +34,21 @@ import org.jetbrains.annotations.NotNull;
public interface MinecraftComponentFactory {
/**
* Creates an empty {@link MinecraftComponent}.
* Creates an {@link MinecraftComponent} from JSON.
*
* @param json the Minecraft component json
* @return a new {@link MinecraftComponent}
*/
@NotNull
MinecraftComponent empty();
MinecraftComponent fromJson(@NotNull String json);
/**
* Create an {@link MinecraftComponentAdapter} for the given GSONComponentSerializer class.
* @param gsonSerializerClass the serializer class
* @return a new {@link MinecraftComponentAdapter}
*/
@NotNull
<T> MinecraftComponentAdapter<T> makeAdapter(Class<?> gsonSerializerClass, @Nullable Class<T> componentClass);
/**
* Creates a EnhancedLegacyText {@link GameTextBuilder} based on the given input.

View File

@ -355,9 +355,6 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage {
@Override
public @NotNull Formatter applyPlaceholderService() {
DiscordSRVApi api = DiscordSRVApi.get();
if (api == null) {
throw new IllegalStateException("DiscordSRVApi not available");
}
this.replacements.put(
PlaceholderService.PATTERN,
wrapFunction(matcher -> api.placeholderService().getResultAsCharSequence(matcher, context))
@ -384,9 +381,6 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage {
@Override
public @NotNull SendableDiscordMessage build() {
DiscordSRVApi api = DiscordSRVApi.get();
if (api == null) {
throw new IllegalStateException("DiscordSRVApi not available");
}
Function<String, String> placeholders = input -> {
if (input == null) {

View File

@ -67,9 +67,6 @@ public interface Module {
@NotNull
default Collection<DiscordGatewayIntent> requiredIntents() {
DiscordSRVApi api = DiscordSRVApi.get();
if (api == null) {
return Collections.emptyList();
}
Collection<? extends EventListener> listeners = api.eventBus().getListeners(this);
EnumSet<DiscordGatewayIntent> intents = EnumSet.noneOf(DiscordGatewayIntent.class);

View File

@ -66,7 +66,7 @@ public class PaperComponentHandle<T> {
unrelocated = handle.invoke(target);
} catch (Throwable ignored) {}
if (unrelocated != null) {
return ComponentUtil.fromUnrelocated((com.discordsrv.unrelocate.net.kyori.adventure.text.Component) unrelocated);
return MinecraftComponent.fromAdventure((com.discordsrv.unrelocate.net.kyori.adventure.text.Component) unrelocated);
}
}

View File

@ -19,7 +19,6 @@
package com.discordsrv.bukkit.console.executor;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.common.component.util.ComponentUtil;
import com.discordsrv.unrelocate.net.kyori.adventure.text.Component;
import org.bukkit.Server;
import org.bukkit.command.CommandSender;
@ -43,7 +42,6 @@ public class PaperCommandFeedbackExecutor implements Consumer<Component> {
@Override
public void accept(Component component) {
MinecraftComponent minecraftComponent = ComponentUtil.fromUnrelocated(component);
componentConsumer.accept(minecraftComponent);
componentConsumer.accept(MinecraftComponent.fromAdventure(component));
}
}

View File

@ -79,7 +79,7 @@ public final class PaperPlayer {
public static void kick(Player player, Component reason) {
try {
KICK_COMPONENT_HANDLE.invokeExact(player, ComponentUtil.toUnrelocated(ComponentUtil.toAPI(reason)));
KICK_COMPONENT_HANDLE.invokeExact(player, ComponentUtil.toAPI(reason).asAdventure());
} catch (Throwable e) {
throw new RuntimeException("Failed to kick player", e);
}

View File

@ -124,7 +124,7 @@ public class BukkitPlayer extends BukkitCommandSender implements IPlayer {
return;
}
try {
PAPER_SEND_MESSAGE_HANDLE.invoke(player, ComponentUtil.toUnrelocated(component));
PAPER_SEND_MESSAGE_HANDLE.invoke(player, component.asAdventure());
} catch (Throwable ignored) {
super.sendMessage(ComponentUtil.fromAPI(component));
}

View File

@ -20,6 +20,7 @@ package com.discordsrv.common.component;
import com.discordsrv.api.component.GameTextBuilder;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.component.MinecraftComponentAdapter;
import com.discordsrv.api.component.MinecraftComponentFactory;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.component.renderer.DiscordSRVMinecraftRenderer;
@ -40,6 +41,7 @@ import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.kyori.ansi.ColorLevel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.Set;
@ -129,8 +131,13 @@ public class ComponentFactory implements MinecraftComponentFactory {
}
@Override
public @NotNull MinecraftComponent empty() {
return MinecraftComponentImpl.empty();
public @NotNull MinecraftComponent fromJson(@NotNull String json) {
return new MinecraftComponentImpl(json);
}
@Override
public <T> @NotNull MinecraftComponentAdapter<T> makeAdapter(Class<?> gsonSerializerClass, @Nullable Class<T> componentClass) {
return new MinecraftComponentAdapterImpl<>(gsonSerializerClass, componentClass);
}
@Override

View File

@ -0,0 +1,85 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2024 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.component;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.component.MinecraftComponentAdapter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MinecraftComponentAdapterImpl<T> implements MinecraftComponentAdapter<T> {
private final Class<?> gsonSerializerClass;
private final Object instance;
private final Method deserialize;
private final Method serialize;
public MinecraftComponentAdapterImpl(Class<?> gsonSerializerClass, Class<T> 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);
}
}
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
);
}
}
@SuppressWarnings("unchecked")
private <R> R execute(Method method, Object input) {
try {
return (R) method.invoke(instance, input);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Failed to invoke " + gsonSerializerClass.getName() + "." + method.getName(), e.getCause());
}
}
@Override
public T toAdventure(MinecraftComponent component) {
String json = component.asJson();
return execute(deserialize, json);
}
@Override
public MinecraftComponent toDiscordSRV(T o) {
String json = execute(serialize, o);
return MinecraftComponent.fromJson(json);
}
}

View File

@ -19,116 +19,38 @@
package com.discordsrv.common.component;
import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.component.MinecraftComponentAdapter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.InvocationTargetException;
public class MinecraftComponentImpl implements MinecraftComponent {
private String json;
private Component component;
public static MinecraftComponentImpl empty() {
return new MinecraftComponentImpl("{text:\"\"}");
}
private final String json;
private final Component component;
public MinecraftComponentImpl(String json) {
setJson(json);
this(GsonComponentSerializer.gson().deserialize(json));
}
public MinecraftComponentImpl(@NotNull Component component) {
setComponent(component);
this.component = component;
this.json = GsonComponentSerializer.gson().serialize(component);
}
public Component getComponent() {
return component;
}
public void setComponent(@NotNull Component component) {
this.component = component;
this.json = GsonComponentSerializer.gson().serialize(component);
}
@Override
public @NotNull String asJson() {
return json;
}
@Override
public void setJson(@NotNull String json) throws IllegalArgumentException {
Component component;
try {
component = GsonComponentSerializer.gson().deserialize(json);
} catch (Throwable t) {
throw new IllegalArgumentException("Provided json is not valid: " + json, t);
}
this.component = component;
this.json = json;
}
@Override
public @NotNull String asPlainString() {
return PlainTextComponentSerializer.plainText().serialize(component);
}
@Override
public <T> MinecraftComponent.@NotNull Adapter<T> adventureAdapter(
@NotNull Class<?> gsonSerializerClass, @NotNull Class<T> componentClass
) {
return new Adapter<>(gsonSerializerClass, componentClass);
}
@Override
public <T> MinecraftComponent.@NotNull Adapter<T> adventureAdapter(@NotNull MinecraftComponentAdapter<T> adapter) {
return new Adapter<>(adapter);
}
@SuppressWarnings("unchecked")
public class Adapter<T> implements MinecraftComponent.Adapter<T> {
private final MinecraftComponentAdapter<T> adapter;
private Adapter(Class<?> gsonSerializerClass, Class<T> componentClass) {
this(MinecraftComponentAdapter.create(gsonSerializerClass, componentClass));
}
private Adapter(MinecraftComponentAdapter<T> adapter) {
this.adapter = adapter;
}
@Override
public @NotNull T getComponent() {
try {
return (T) adapter.deserializeMethod()
.invoke(
adapter.serializerInstance(),
json
);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("Failed to convert to adventure component", e);
}
}
@Override
public void setComponent(@NotNull Object adventureComponent) {
try {
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.serializerClass().getName(), e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Failed to convert from adventure component", e);
}
}
}
}

View File

@ -22,30 +22,26 @@ import com.discordsrv.api.component.MinecraftComponent;
import com.discordsrv.api.component.MinecraftComponentAdapter;
import com.discordsrv.common.component.MinecraftComponentImpl;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* An util class for {@link Component}s and {@link MinecraftComponent}s.
* A util class for {@link Component}s and {@link MinecraftComponent}s.
*/
public final class ComponentUtil {
private ComponentUtil() {}
private static MinecraftComponentAdapter<Component> ADAPTER;
@NotNull
private static MinecraftComponentAdapter<Component> getAdapter() {
private static MinecraftComponentAdapter<Component> adapter() {
return ADAPTER != null ? ADAPTER : (ADAPTER = MinecraftComponentAdapter.create(GsonComponentSerializer.class, Component.class));
}
private ComponentUtil() {}
public static boolean isEmpty(@NotNull Component component) {
return PlainTextComponentSerializer.plainText().serialize(component).isEmpty();
}
@ -78,48 +74,7 @@ public final class ComponentUtil {
if (component instanceof MinecraftComponentImpl) {
return ((MinecraftComponentImpl) component).getComponent();
} else {
return component.adventureAdapter(getAdapter()).getComponent();
return component.asAdventure(adapter());
}
}
public static void set(MinecraftComponent minecraftComponent, Component component) {
if (component instanceof MinecraftComponentImpl) {
((MinecraftComponentImpl) component).setComponent(component);
} else {
minecraftComponent.adventureAdapter(getAdapter()).setComponent(component);
}
}
public static MinecraftComponent fromUnrelocated(@NotNull com.discordsrv.unrelocate.net.kyori.adventure.text.Component unrelocatedAdventure) {
MinecraftComponentImpl component = MinecraftComponentImpl.empty();
MinecraftComponent.Adapter<Object> adapter = component.unrelocatedAdapter();
if (adapter == null) {
throw new IllegalStateException("Could not get unrelocated adventure gson serializer");
}
adapter.setComponent(unrelocatedAdventure);
return component;
}
public static com.discordsrv.unrelocate.net.kyori.adventure.text.Component toUnrelocated(MinecraftComponent component) {
MinecraftComponent.Adapter<Object> adapter = component.unrelocatedAdapter();
if (adapter == null) {
throw new IllegalStateException("Could not get unrelocated adventure gson serializer");
}
return (com.discordsrv.unrelocate.net.kyori.adventure.text.Component) adapter.getComponent();
}
public static Component join(@NotNull Component delimiter, @NotNull Collection<? extends ComponentLike> components) {
return join(delimiter, components.toArray(new ComponentLike[0]));
}
public static Component join(@NotNull Component delimiter, @NotNull ComponentLike[] components) {
TextComponent.Builder builder = Component.text();
for (int i = 0; i < components.length; i++) {
builder.append(components[i]);
if (i < components.length - 1) {
builder.append(delimiter);
}
}
return builder.build();
}
}

View File

@ -37,11 +37,7 @@ public class MinecraftMessage {
}
public GameTextBuilder textBuilder() {
DiscordSRVApi discordSRV = DiscordSRVApi.get();
if (discordSRV == null) {
throw new IllegalStateException("DiscordSRVApi == null");
}
return discordSRV.componentFactory().textBuilder(rawFormat);
return DiscordSRVApi.get().componentFactory().textBuilder(rawFormat);
}
public MinecraftComponent make() {

View File

@ -27,10 +27,10 @@ import com.discordsrv.api.placeholder.annotation.Placeholder;
import com.discordsrv.api.placeholder.annotation.PlaceholderPrefix;
import com.discordsrv.api.placeholder.annotation.PlaceholderRemainder;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.component.util.ComponentUtil;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.format.TextColor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -159,7 +159,7 @@ public class DiscordGuildMemberImpl implements DiscordGuildMember {
components.add(Component.text(role.getName()).color(TextColor.color(role.getColor().rgb())));
}
return ComponentUtil.join(Component.text(suffix), components);
return Component.join(JoinConfiguration.separator(Component.text(suffix)), components);
}
@Override

View File

@ -42,6 +42,7 @@ import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.WebhookClient;
import net.dv8tion.jda.api.requests.ErrorResponse;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
@ -307,7 +308,7 @@ public class ReceivedDiscordMessageImpl implements ReceivedDiscordMessage {
));
}
return ComponentUtil.join(Component.text(suffix), components);
return Component.join(JoinConfiguration.separator(Component.text(suffix)), components);
}
@Override