messages = new ArrayList<>();
if (data.isValue()) {
Object raw = data.getValueRaw();
if (raw instanceof String) {
- messages.add(data.getValue(RawComponent.class));
+ messages.add(data.getValue(Component.class));
} else if (raw instanceof List) {
- messages.addAll(data.getValueAsList(RawComponent.class));
+ messages.addAll(data.getValueAsList(Component.class));
}
} else {
onlyConsole = data.containsKey("console")
@@ -54,9 +54,9 @@ public ChatHolder deserialize(DeserializationData data, GenericsDeclaration gene
Object raw = data.getRaw("message");
if (raw instanceof String) {
- messages.add(data.get("message", RawComponent.class));
+ messages.add(data.get("message", Component.class));
} else if (raw instanceof List) {
- messages.addAll(data.getAsList("message", RawComponent.class));
+ messages.addAll(data.getAsList("message", Component.class));
}
}
diff --git a/repository/okaeri/src/main/java/dev/peri/yetanothermessageslibrary/config/serdes/holder/SoundHolderSerializer.java b/repository/okaeri/src/main/java/dev/peri/yetanothermessageslibrary/config/serdes/holder/SoundHolderSerializer.java
index 286aa75..0f1dacb 100644
--- a/repository/okaeri/src/main/java/dev/peri/yetanothermessageslibrary/config/serdes/holder/SoundHolderSerializer.java
+++ b/repository/okaeri/src/main/java/dev/peri/yetanothermessageslibrary/config/serdes/holder/SoundHolderSerializer.java
@@ -18,12 +18,12 @@ public boolean supports(Class super SoundHolder> type) {
@Override
public void serialize(SoundHolder holder, SerializationData data, GenericsDeclaration generics) {
Sound sound = holder.getSound();
- data.add("name", sound.name().asString());
- data.add("source", sound.source());
- data.add("volume", sound.volume());
- data.add("pitch", sound.pitch());
+ data.add("name", sound.name().asString(), String.class);
+ data.add("source", sound.source(), Sound.Source.class);
+ data.add("volume", sound.volume(), float.class);
+ data.add("pitch", sound.pitch(), float.class);
if (holder.stopOtherSounds()) {
- data.add("stop-other-sounds", true);
+ data.add("stop-other-sounds", true, boolean.class);
}
}
diff --git a/repository/okaeri/src/main/java/dev/peri/yetanothermessageslibrary/config/serdes/holder/TitleHolderSerializer.java b/repository/okaeri/src/main/java/dev/peri/yetanothermessageslibrary/config/serdes/holder/TitleHolderSerializer.java
index ac479f9..37a0391 100644
--- a/repository/okaeri/src/main/java/dev/peri/yetanothermessageslibrary/config/serdes/holder/TitleHolderSerializer.java
+++ b/repository/okaeri/src/main/java/dev/peri/yetanothermessageslibrary/config/serdes/holder/TitleHolderSerializer.java
@@ -1,12 +1,12 @@
package dev.peri.yetanothermessageslibrary.config.serdes.holder;
-import dev.peri.yetanothermessageslibrary.adventure.RawComponent;
import dev.peri.yetanothermessageslibrary.message.holder.impl.TitleHolder;
import eu.okaeri.configs.schema.GenericsDeclaration;
import eu.okaeri.configs.serdes.DeserializationData;
import eu.okaeri.configs.serdes.ObjectSerializer;
import eu.okaeri.configs.serdes.SerializationData;
import java.time.Duration;
+import net.kyori.adventure.text.Component;
import net.kyori.adventure.title.Title.Times;
import net.kyori.adventure.util.Ticks;
@@ -19,11 +19,14 @@ public boolean supports(Class super TitleHolder> type) {
@Override
public void serialize(TitleHolder holder, SerializationData data, GenericsDeclaration generics) {
- if (!holder.getTitle().getRaw().isEmpty()) {
- data.add("title", holder.getTitle().getRaw());
+ Component title = holder.getTitle();
+ if (!title.equals(Component.empty())) {
+ data.add("title", title, Component.class);
}
- if (!holder.getSubTitle().getRaw().isEmpty()) {
- data.add("subtitle", holder.getSubTitle().getRaw());
+
+ Component subtitle = holder.getSubTitle();
+ if (!subtitle.equals(Component.empty())) {
+ data.add("subtitle", subtitle, Component.class);
}
Times times = holder.getTimes();
@@ -32,15 +35,15 @@ public void serialize(TitleHolder holder, SerializationData data, GenericsDeclar
int fadeOut = ticksFromDuration(times.fadeOut());
if (fadeIn > 0) {
- data.add("fade-in", fadeIn);
+ data.add("fade-in", fadeIn, int.class);
}
if (stay > 0) {
- data.add("stay", stay);
+ data.add("stay", stay, int.class);
}
if (fadeOut > 0) {
- data.add("fade-out", fadeOut);
+ data.add("fade-out", fadeOut, int.class);
}
}
@@ -49,11 +52,11 @@ public TitleHolder deserialize(DeserializationData data, GenericsDeclaration gen
TitleHolder.Builder builder = TitleHolder.builder();
if (data.containsKey("title")) {
- builder.title(data.get("title", RawComponent.class));
+ builder.title(data.get("title", Component.class));
}
if (data.containsKey("subtitle")) {
- builder.subTitle(data.get("subtitle", RawComponent.class));
+ builder.subTitle(data.get("subtitle", Component.class));
}
int fadeIn = 0;
diff --git a/settings.gradle b/settings.gradle
index a07449a..76aaac7 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,5 +1,6 @@
rootProject.name = 'YetAnotherMessagesLibrary'
+this.setupSubproject("tools")
this.setupSubproject("core")
// Platforms
@@ -37,10 +38,10 @@ dependencyResolutionManagement {
library("velocity", "com.velocitypowered:velocity-api:3.1.1")
}
adventure {
- String apiVersion = "4.14.0"
+ String apiVersion = "4.16.0"
library("api", "net.kyori:adventure-api:${apiVersion}")
- library("minimessage", "net.kyori:adventure-text-minimessage:${apiVersion}")
library("serializer-legacy", "net.kyori:adventure-text-serializer-legacy:${apiVersion}")
+ library("serializer-minimessage", "net.kyori:adventure-text-minimessage:${apiVersion}")
String platformVersion = "4.3.1"
library("platform-bukkit", "net.kyori:adventure-platform-bukkit:${platformVersion}")
diff --git a/tools/build.gradle b/tools/build.gradle
new file mode 100644
index 0000000..f0bf4f3
--- /dev/null
+++ b/tools/build.gradle
@@ -0,0 +1,5 @@
+dependencies {
+ compileOnlyApi adventure.api
+ compileOnlyApi adventure.serializer.legacy
+ compileOnlyApi adventure.serializer.minimessage
+}
\ No newline at end of file
diff --git a/tools/src/main/java/dev/peri/yetanothermessageslibrary/adventure/GlobalAdventureSerializer.java b/tools/src/main/java/dev/peri/yetanothermessageslibrary/adventure/GlobalAdventureSerializer.java
new file mode 100644
index 0000000..ced393d
--- /dev/null
+++ b/tools/src/main/java/dev/peri/yetanothermessageslibrary/adventure/GlobalAdventureSerializer.java
@@ -0,0 +1,91 @@
+package dev.peri.yetanothermessageslibrary.adventure;
+
+import dev.peri.yetanothermessageslibrary.replace.replacement.FunctionStringReplacement;
+import dev.peri.yetanothermessageslibrary.replace.replacement.SimpleStringReplacement;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.serializer.ComponentSerializer;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Simple utility class to provide global access to adventure serializer.
+ *
+ * This class is used mostly by string replacements ({@link SimpleStringReplacement}, {@link FunctionStringReplacement})
+ * and some of {@link dev.peri.yetanothermessageslibrary.message.holder.SendableHolder} implementations.
+ */
+public final class GlobalAdventureSerializer {
+
+ private static ComponentSerializer GLOBAL_SERIALIZER = new ComponentSerializer() {
+ @Override
+ public @NotNull Component deserialize(@NotNull String input) {
+ return Component.text(input);
+ }
+
+ @Override
+ public @NotNull String serialize(@NotNull Component component) {
+ throw new UnsupportedOperationException("Register global serializer to serialize components");
+ }
+ };
+
+ private GlobalAdventureSerializer() {
+ }
+
+ static {
+ boolean isNativeLegacySerializer = false;
+ boolean isNativeMiniMessageSerializer = false;
+
+ try {
+ Class.forName("net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer");
+ isNativeLegacySerializer = true;
+ } catch (ClassNotFoundException ignored) {
+ }
+
+ try {
+ Class.forName("net.kyori.adventure.text.serializer.minimessage.MiniMessage");
+ isNativeMiniMessageSerializer = true;
+ } catch (ClassNotFoundException ignored) {
+ }
+
+ if (isNativeLegacySerializer) {
+ NativeLegacySerializer.init();
+ } else if (isNativeMiniMessageSerializer) {
+ NativeMiniMessageSerializer.init();
+ }
+ }
+
+ public static @NotNull ComponentSerializer globalSerializer() {
+ return GLOBAL_SERIALIZER;
+ }
+
+ public static void globalSerializer(@NotNull ComponentSerializer globalSerializer) {
+ GLOBAL_SERIALIZER = Objects.requireNonNull(globalSerializer, "globalSerializer cannot be null");
+ }
+
+ public static @NotNull Component deserialize(@NotNull String input) {
+ return GLOBAL_SERIALIZER.deserialize(input);
+ }
+
+ public static @NotNull List deserialize(@NotNull List input) {
+ return input.stream().map(GlobalAdventureSerializer::deserialize).collect(Collectors.toList());
+ }
+
+ public static @NotNull List deserialize(@NotNull String... input) {
+ return deserialize(Arrays.asList(input));
+ }
+
+ public static @NotNull Component[] deserializeArray(@NotNull List input) {
+ return deserialize(input).toArray(new Component[0]);
+ }
+
+ public static @NotNull Component[] deserializeArray(@NotNull String... input) {
+ return deserialize(input).toArray(new Component[0]);
+ }
+
+ public static @NotNull String serialize(@NotNull Component component) {
+ return GLOBAL_SERIALIZER.serialize(component);
+ }
+
+}
diff --git a/tools/src/main/java/dev/peri/yetanothermessageslibrary/adventure/NativeLegacySerializer.java b/tools/src/main/java/dev/peri/yetanothermessageslibrary/adventure/NativeLegacySerializer.java
new file mode 100644
index 0000000..970cfa9
--- /dev/null
+++ b/tools/src/main/java/dev/peri/yetanothermessageslibrary/adventure/NativeLegacySerializer.java
@@ -0,0 +1,14 @@
+package dev.peri.yetanothermessageslibrary.adventure;
+
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
+
+final class NativeLegacySerializer {
+
+ private NativeLegacySerializer() {
+ }
+
+ static void init() {
+ GlobalAdventureSerializer.globalSerializer(LegacyComponentSerializer.legacySection());
+ }
+
+}
diff --git a/tools/src/main/java/dev/peri/yetanothermessageslibrary/adventure/NativeMiniMessageSerializer.java b/tools/src/main/java/dev/peri/yetanothermessageslibrary/adventure/NativeMiniMessageSerializer.java
new file mode 100644
index 0000000..71597e8
--- /dev/null
+++ b/tools/src/main/java/dev/peri/yetanothermessageslibrary/adventure/NativeMiniMessageSerializer.java
@@ -0,0 +1,14 @@
+package dev.peri.yetanothermessageslibrary.adventure;
+
+import net.kyori.adventure.text.minimessage.MiniMessage;
+
+final class NativeMiniMessageSerializer {
+
+ private NativeMiniMessageSerializer() {
+ }
+
+ static void init() {
+ GlobalAdventureSerializer.globalSerializer(MiniMessage.miniMessage());
+ }
+
+}
diff --git a/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/ComponentReplacer.java b/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/ComponentReplacer.java
new file mode 100644
index 0000000..588a365
--- /dev/null
+++ b/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/ComponentReplacer.java
@@ -0,0 +1,69 @@
+package dev.peri.yetanothermessageslibrary.replace;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import net.kyori.adventure.text.Component;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public final class ComponentReplacer {
+
+ private ComponentReplacer() {
+ }
+
+ @Contract(pure = true, value = "_, null, _ -> null")
+ public static Component replace(@Nullable Locale locale, @Nullable Component text, @NotNull Iterable extends Replaceable> replacements) {
+ if (text == null) {
+ return null;
+ }
+
+ for (Replaceable replacement : replacements) {
+ text = replacement.replace(locale, text);
+ }
+ return text;
+ }
+
+ @Contract(pure = true, value = "null, _, -> null")
+ public static Component replace(@Nullable Component text, @NotNull Iterable extends Replaceable> replacements) {
+ return replace(null, text, replacements);
+ }
+
+ @Contract(pure = true, value = "_, null, _ -> null")
+ public static Component replace(@Nullable Locale locale, @Nullable Component text, @NotNull Replaceable... replacements) {
+ return replace(locale, text, Arrays.asList(replacements));
+ }
+
+ @Contract(pure = true, value = "null, _, -> null")
+ public static Component replace(@Nullable Component text, @NotNull Replaceable... replacements) {
+ return replace(null, text, replacements);
+ }
+
+ @Contract(pure = true)
+ public static @NotNull List replace(@Nullable Locale locale, @NotNull Iterable extends Component> text, @NotNull Iterable extends Replaceable> replacements) {
+ List result = new ArrayList<>();
+ for (Component line : text) {
+ result.add(replace(locale, line, replacements));
+ }
+ return result;
+ }
+
+ @Contract(pure = true)
+ public static @NotNull List replace(@NotNull Iterable extends Component> text, @NotNull Collection extends Replaceable> replacements) {
+ return replace(null, text, replacements);
+ }
+
+ @Contract(pure = true)
+ public static @NotNull List replace(@Nullable Locale locale, @NotNull Iterable extends Component> text, @NotNull Replaceable... replacements) {
+ return replace(locale, text, Arrays.asList(replacements));
+ }
+
+ @Contract(pure = true)
+ public static @NotNull List replace(@NotNull Iterable extends Component> text, @NotNull Replaceable... replacements) {
+ return replace(null, text, replacements);
+ }
+
+}
diff --git a/core/src/main/java/dev/peri/yetanothermessageslibrary/replace/Replaceable.java b/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/Replaceable.java
similarity index 70%
rename from core/src/main/java/dev/peri/yetanothermessageslibrary/replace/Replaceable.java
rename to tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/Replaceable.java
index d7cc572..40f562d 100644
--- a/core/src/main/java/dev/peri/yetanothermessageslibrary/replace/Replaceable.java
+++ b/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/Replaceable.java
@@ -7,12 +7,6 @@
public interface Replaceable {
- @NotNull String replace(@Nullable Locale locale, @NotNull String text);
-
- default @NotNull String replace(@NotNull String text) {
- return this.replace(null, text);
- }
-
@NotNull Component replace(@Nullable Locale locale, @NotNull Component text);
default @NotNull Component replace(@NotNull Component text) {
diff --git a/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/StringReplaceable.java b/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/StringReplaceable.java
new file mode 100644
index 0000000..ae0da7c
--- /dev/null
+++ b/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/StringReplaceable.java
@@ -0,0 +1,15 @@
+package dev.peri.yetanothermessageslibrary.replace;
+
+import java.util.Locale;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public interface StringReplaceable extends Replaceable {
+
+ @NotNull String replace(@Nullable Locale locale, @NotNull String text);
+
+ default @NotNull String replace(@NotNull String text) {
+ return this.replace(null, text);
+ }
+
+}
diff --git a/core/src/main/java/dev/peri/yetanothermessageslibrary/replace/StringReplacer.java b/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/StringReplacer.java
similarity index 60%
rename from core/src/main/java/dev/peri/yetanothermessageslibrary/replace/StringReplacer.java
rename to tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/StringReplacer.java
index d8cbe02..b37bfd2 100644
--- a/core/src/main/java/dev/peri/yetanothermessageslibrary/replace/StringReplacer.java
+++ b/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/StringReplacer.java
@@ -2,7 +2,6 @@
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.List;
import java.util.Locale;
import org.jetbrains.annotations.Contract;
@@ -15,35 +14,34 @@ private StringReplacer() {
}
@Contract(pure = true, value = "_, null, _, -> null")
- public static String replace(@Nullable Locale locale, @Nullable String text, @NotNull Collection extends Replaceable> replacements) {
+ public static String replace(@Nullable Locale locale, @Nullable String text, @NotNull Iterable extends StringReplaceable> replacements) {
if (text == null || text.isEmpty()) {
return text;
}
- for (Replaceable replacement : replacements) {
+ for (StringReplaceable replacement : replacements) {
text = replacement.replace(locale, text);
}
-
return text;
}
@Contract(pure = true, value = "null, _, -> null")
- public static String replace(@Nullable String text, @NotNull Collection extends Replaceable> replacements) {
+ public static String replace(@Nullable String text, @NotNull Iterable extends StringReplaceable> replacements) {
return replace(null, text, replacements);
}
@Contract(pure = true, value = "_, null, _, -> null")
- public static String replace(@Nullable Locale locale, @Nullable String text, @NotNull Replaceable... replacements) {
+ public static String replace(@Nullable Locale locale, @Nullable String text, @NotNull StringReplaceable... replacements) {
return replace(locale, text, Arrays.asList(replacements));
}
@Contract(pure = true, value = "null, _, -> null")
- public static String replace(@Nullable String text, @NotNull Replaceable... replacements) {
+ public static String replace(@Nullable String text, @NotNull StringReplaceable... replacements) {
return replace(null, text, replacements);
}
@Contract(pure = true)
- public static @NotNull List replace(@Nullable Locale locale, @NotNull List text, @NotNull Replaceable... replacements) {
+ public static @NotNull List replace(@Nullable Locale locale, @NotNull Iterable text, @NotNull Iterable extends StringReplaceable> replacements) {
List result = new ArrayList<>();
for (String line : text) {
result.add(replace(locale, line, replacements));
@@ -52,8 +50,18 @@ public static String replace(@Nullable String text, @NotNull Replaceable... repl
}
@Contract(pure = true)
- public static @NotNull List replace(@NotNull List text, @NotNull Replaceable... replacements) {
+ public static @NotNull List replace(@NotNull Iterable text, @NotNull Iterable extends StringReplaceable> replacements) {
return replace(null, text, replacements);
}
+ @Contract(pure = true)
+ public static @NotNull List replace(@NotNull Iterable text, @NotNull StringReplaceable... replacements) {
+ return replace(null, text, Arrays.asList(replacements));
+ }
+
+ @Contract(pure = true)
+ public static @NotNull List replace(@Nullable Locale locale, @NotNull Iterable text, @NotNull StringReplaceable... replacements) {
+ return replace(locale, text, Arrays.asList(replacements));
+ }
+
}
diff --git a/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/replacement/ComponentReplacement.java b/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/replacement/ComponentReplacement.java
new file mode 100644
index 0000000..6dbf489
--- /dev/null
+++ b/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/replacement/ComponentReplacement.java
@@ -0,0 +1,22 @@
+package dev.peri.yetanothermessageslibrary.replace.replacement;
+
+import java.util.Locale;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.TextReplacementConfig;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public abstract class ComponentReplacement extends Replacement {
+
+ protected ComponentReplacement(@NotNull Object placeholder) {
+ super(placeholder);
+ }
+
+ public abstract @NotNull TextReplacementConfig getReplacement(@Nullable Locale locale);
+
+ @Override
+ public @NotNull Component replace(@Nullable Locale locale, @NotNull Component text) {
+ return text.replaceText(this.getReplacement(locale));
+ }
+
+}
diff --git a/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/replacement/FunctionReplacement.java b/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/replacement/FunctionReplacement.java
new file mode 100644
index 0000000..b23a812
--- /dev/null
+++ b/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/replacement/FunctionReplacement.java
@@ -0,0 +1,31 @@
+package dev.peri.yetanothermessageslibrary.replace.replacement;
+
+import java.util.Locale;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import net.kyori.adventure.text.ComponentLike;
+import net.kyori.adventure.text.TextReplacementConfig;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class FunctionReplacement extends ComponentReplacement {
+
+ private final Function<@Nullable Locale, ? extends @NotNull ComponentLike> replacementFunction;
+
+ protected FunctionReplacement(@NotNull Object placeholder, @NotNull Function<@Nullable Locale, ? extends @NotNull ComponentLike> replacementFunction) {
+ super(placeholder);
+ this.replacementFunction = replacementFunction;
+ }
+
+ protected FunctionReplacement(@NotNull Object placeholder, @NotNull Supplier extends @NotNull ComponentLike> replacementSupplier) {
+ this(placeholder, locale -> replacementSupplier.get());
+ }
+
+ @Override
+ public @NotNull TextReplacementConfig getReplacement(@Nullable Locale locale) {
+ return this.newReplacementBuilder()
+ .replacement(this.replacementFunction.apply(locale))
+ .build();
+ }
+
+}
diff --git a/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/replacement/FunctionStringReplacement.java b/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/replacement/FunctionStringReplacement.java
new file mode 100644
index 0000000..a0f52b0
--- /dev/null
+++ b/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/replacement/FunctionStringReplacement.java
@@ -0,0 +1,30 @@
+package dev.peri.yetanothermessageslibrary.replace.replacement;
+
+import dev.peri.yetanothermessageslibrary.adventure.GlobalAdventureSerializer;
+import dev.peri.yetanothermessageslibrary.replace.StringReplaceable;
+import java.util.Locale;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class FunctionStringReplacement extends FunctionReplacement implements StringReplaceable {
+
+ private final Function<@Nullable Locale, ? extends @NotNull String> replacementFunction;
+
+ protected FunctionStringReplacement(@NotNull Object placeholder, @NotNull Function<@Nullable Locale, @NotNull String> replacementFunction) {
+ super(placeholder, locale -> GlobalAdventureSerializer.deserialize(replacementFunction.apply(locale)));
+ this.replacementFunction = replacementFunction;
+ }
+
+ protected FunctionStringReplacement(@NotNull Object placeholder, @NotNull Supplier<@NotNull String> replacementSupplier) {
+ super(placeholder, locale -> GlobalAdventureSerializer.deserialize(replacementSupplier.get()));
+ this.replacementFunction = locale -> replacementSupplier.get();
+ }
+
+ @Override
+ public @NotNull String replace(@Nullable Locale locale, @NotNull String text) {
+ return this.getPlaceholderType().replace(text, this.getPlaceholder(), this.replacementFunction.apply(locale));
+ }
+
+}
diff --git a/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/replacement/Replacement.java b/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/replacement/Replacement.java
new file mode 100644
index 0000000..2b86808
--- /dev/null
+++ b/tools/src/main/java/dev/peri/yetanothermessageslibrary/replace/replacement/Replacement.java
@@ -0,0 +1,155 @@
+package dev.peri.yetanothermessageslibrary.replace.replacement;
+
+import dev.peri.yetanothermessageslibrary.replace.Replaceable;
+import dev.peri.yetanothermessageslibrary.util.TriFunction;
+import dev.peri.yetanothermessageslibrary.util.Validate;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+import net.kyori.adventure.text.ComponentLike;
+import net.kyori.adventure.text.TextReplacementConfig;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public abstract class Replacement implements Replaceable {
+
+ private final PlaceholderType placeholderType;
+ private final Object placeholder;
+
+ protected Replacement(@NotNull Object placeholder) {
+ PlaceholderType placeholderType = PlaceholderType.findType(placeholder.getClass());
+ Validate.isTrue(placeholderType != null, "placeholder must be a String or Pattern");
+ this.placeholderType = placeholderType;
+ this.placeholder = placeholder;
+ }
+
+ /**
+ * @return the type of placeholder (String or Pattern)
+ */
+ protected final @NotNull Replacement.PlaceholderType getPlaceholderType() {
+ return this.placeholderType;
+ }
+
+ /**
+ * @return a new TextReplacementConfig.Builder that matches the placeholder
+ */
+ protected final @NotNull TextReplacementConfig.Builder newReplacementBuilder() {
+ return this.placeholderType.newReplacementBuilder(this.placeholder);
+ }
+
+ /**
+ * @return the placeholder object to match (String or Pattern)
+ */
+ protected final @NotNull Object getPlaceholder() {
+ return this.placeholder;
+ }
+
+ protected enum PlaceholderType {
+
+ STRING(
+ String.class,
+ (text, placeholder, replacement) -> text.replace((String) placeholder, replacement),
+ replacement -> TextReplacementConfig.builder().matchLiteral((String) replacement)
+ ),
+ REGEX(
+ Pattern.class,
+ (text, placeholder, replacement) -> ((Pattern) placeholder).matcher(text).replaceAll(replacement),
+ replacement -> TextReplacementConfig.builder().match((Pattern) replacement)
+ );
+
+ private static final Map, PlaceholderType> PLACEHOLDERS_MAP;
+
+ static {
+ Map, PlaceholderType> map = new HashMap<>();
+ for (PlaceholderType placeholderType : PlaceholderType.values()) {
+ map.put(placeholderType.getClazz(), placeholderType);
+ }
+ PLACEHOLDERS_MAP = Collections.unmodifiableMap(map);
+ }
+
+ private final Class> clazz;
+ private final TriFunction replacer;
+ private final Function