From 7483a1753600a6ead4099abce6646b7492486b62 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafa=C5=82=20Chomczyk?=
<63915083+rchomczyk@users.noreply.github.com>
Date: Tue, 19 Nov 2024 01:54:05 +0100
Subject: [PATCH] Snapshot 2.1.0 (#27)
* Fix code style inconsistencies
* Improve dispatching flow and internal encapsulation
* Add delegating placeholder sanitizer
* Add more efficient implementation for placeholder sanitizer for adventure
* Decomposition of functions, support dry kiss principles / noyzys
---
.idea/gradle.xml | 1 +
README.md | 18 ++--
.../src/main/kotlin/honey-publish.gradle.kts | 2 +-
.../compiler/AdventureMessageCompiler.java | 11 +--
.../AdventureTitleMessageDispatcher.java | 59 +++++++-----
.../formatter/AdventureMessageFormatter.java | 2 +-
.../AdventureMessageFormatterFactory.java | 6 +-
.../AdventurePlaceholderSanitizer.java | 42 +++++++++
.../AdventurePlaceholderSanitizerFactory.java | 16 ++++
...ventureReflectivePlaceholderSanitizer.java | 92 +++++++++++++++++++
...ntureReflectiveTransformationPipeline.java | 19 ++++
.../src/dev/shiza/honey/message/Message.java | 8 +-
.../message/compiler/MessageCompiler.java | 2 +-
.../dispatcher/MessageBaseDispatcher.java | 77 +++++-----------
.../message/dispatcher/MessageDispatcher.java | 39 ++------
.../message/dispatcher/MessageRenderer.java | 53 ++++-------
.../dispatcher/TitleMessageDispatcher.java | 17 ++--
.../formatter/MessageBaseFormatter.java | 4 +-
.../{evaluator => }/PlaceholderContext.java | 35 +++----
.../evaluator/EvaluatedPlaceholder.java | 5 -
.../evaluator/PlaceholderEvaluator.java | 6 +-
.../ReflectivePlaceholderEvaluator.java | 5 +-
.../visitor/PlaceholderVisitingException.java | 8 ++
.../evaluator/visitor/PlaceholderVisitor.java | 88 ++++++++++++++++++
.../processor/PlaceholderProcessor.java | 4 +-
.../processor/PlaceholderProcessorImpl.java | 13 ++-
.../resolver/PlaceholderResolver.java | 1 +
.../DelegatingPlaceholderSanitizer.java | 27 ++++++
...DelegatingPlaceholderSanitizerFactory.java | 10 ++
.../sanitizer/PlaceholderSanitizer.java | 15 ++-
.../PlaceholderSanitizerFactory.java | 10 --
.../sanitizer/PlaceholderSanitizerImpl.java | 88 ------------------
.../sanitizer/SanitizedPlaceholder.java | 3 -
.../visitor/PlaceholderVisitor.java | 14 ---
.../visitor/PlaceholderVisitorImpl.java | 38 --------
.../visitor/PromisingPlaceholderVisitor.java | 39 --------
.../AdventurePlaceholderSanitizerTest.java | 46 ++++++++++
...dventurePlaceholderSanitizerTestUtils.java | 19 ++++
...reReflectivePlaceholderSanitizerTest.java} | 19 ++--
...Tests.java => PlaceholderContextTest.java} | 16 +++-
.../ReflectivePlaceholderEvaluatorTest.java | 35 ++++---
...flectivePlaceholderEvaluatorTestUtils.java | 7 +-
.../RegexPlaceholderResolverTest.java | 2 +-
.../PlaceholderSanitizerImplTestUtils.java | 15 ---
.../src/dev/shiza/honey/title.kt | 8 +-
.../src/dev/shiza/honey/ExampleListener.java | 19 ++--
settings.gradle.kts | 2 +-
47 files changed, 586 insertions(+), 479 deletions(-)
create mode 100644 honey-common/src/dev/shiza/honey/adventure/placeholder/sanitizer/AdventurePlaceholderSanitizer.java
create mode 100644 honey-common/src/dev/shiza/honey/adventure/placeholder/sanitizer/AdventurePlaceholderSanitizerFactory.java
create mode 100644 honey-common/src/dev/shiza/honey/adventure/placeholder/sanitizer/AdventureReflectivePlaceholderSanitizer.java
create mode 100644 honey-common/src/dev/shiza/honey/adventure/placeholder/sanitizer/AdventureReflectiveTransformationPipeline.java
rename honey-common/src/dev/shiza/honey/placeholder/{evaluator => }/PlaceholderContext.java (59%)
delete mode 100644 honey-common/src/dev/shiza/honey/placeholder/evaluator/EvaluatedPlaceholder.java
create mode 100644 honey-common/src/dev/shiza/honey/placeholder/evaluator/visitor/PlaceholderVisitingException.java
create mode 100644 honey-common/src/dev/shiza/honey/placeholder/evaluator/visitor/PlaceholderVisitor.java
create mode 100644 honey-common/src/dev/shiza/honey/placeholder/sanitizer/DelegatingPlaceholderSanitizer.java
create mode 100644 honey-common/src/dev/shiza/honey/placeholder/sanitizer/DelegatingPlaceholderSanitizerFactory.java
delete mode 100644 honey-common/src/dev/shiza/honey/placeholder/sanitizer/PlaceholderSanitizerFactory.java
delete mode 100644 honey-common/src/dev/shiza/honey/placeholder/sanitizer/PlaceholderSanitizerImpl.java
delete mode 100644 honey-common/src/dev/shiza/honey/placeholder/sanitizer/SanitizedPlaceholder.java
delete mode 100644 honey-common/src/dev/shiza/honey/placeholder/visitor/PlaceholderVisitor.java
delete mode 100644 honey-common/src/dev/shiza/honey/placeholder/visitor/PlaceholderVisitorImpl.java
delete mode 100644 honey-common/src/dev/shiza/honey/placeholder/visitor/PromisingPlaceholderVisitor.java
create mode 100644 honey-common/test/dev/shiza/honey/placeholder/adventure/placeholder/sanitizer/AdventurePlaceholderSanitizerTest.java
create mode 100644 honey-common/test/dev/shiza/honey/placeholder/adventure/placeholder/sanitizer/AdventurePlaceholderSanitizerTestUtils.java
rename honey-common/test/dev/shiza/honey/placeholder/{sanitizer/PlaceholderSanitizerImplTest.java => adventure/placeholder/sanitizer/AdventureReflectivePlaceholderSanitizerTest.java} (75%)
rename honey-common/test/dev/shiza/honey/placeholder/evaluator/{PlaceholderContextTests.java => PlaceholderContextTest.java} (63%)
delete mode 100644 honey-common/test/dev/shiza/honey/placeholder/sanitizer/PlaceholderSanitizerImplTestUtils.java
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 4a3641f..bb8d63b 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -23,6 +23,7 @@
+
diff --git a/README.md b/README.md
index cd01d44..4aaf3d4 100644
--- a/README.md
+++ b/README.md
@@ -63,7 +63,7 @@ hasle on developer side.
```java
AdventureMessageDispatcher.createTitle()
- .recipient(event.getPlayer())
+ .viewer(event.getPlayer())
.title(it -> it.template(formatter, "Hello!"))
.subtitle(it -> it.template(formatter, "It is a pleasure to see you there {{player.getName}}"))
.variable("player", event.getPlayer())
@@ -71,13 +71,13 @@ AdventureMessageDispatcher.createTitle()
.dispatch();
AdventureMessageDispatcher.createChat()
- .recipient(Bukkit.getServer())
+ .viewer(Bukkit.getServer())
.template(formatter, "{{player.getName}} has joined the server!")
.variable("player", event.getPlayer())
.dispatch();
AdventureMessageDispatcher.createActionBar()
- .recipient(event.getPlayer())
+ .viewer(event.getPlayer())
.template(formatter, "Honey is great, isn't it?")
.dispatch();
```
@@ -88,12 +88,12 @@ AdventureMessageDispatcher.createActionBar()
```java
AdventureMessageDispatcher.createChat()
- .recipient(Bukkit.getServer())
+ .viewer(Bukkit.getServer())
.template(Component.text("Somebody joined to the server!").color(NamedTextColor.RED))
.dispatch();
AdventureMessageDispatcher.createActionBar()
- .recipient(event.getPlayer())
+ .viewer(event.getPlayer())
.template(Component.text("Honey is great, isn't it?"))
.dispatch();
```
@@ -101,12 +101,12 @@ AdventureMessageDispatcher.createActionBar()
### Use case (honey-kt-extesion)
```kotlin
AdventureMessageDispatcher.createChat()
- .recipient(event.player)
+ .viewer(event.player)
.template(Component.text("Hello!"))
.dispatch()
AdventureMessageDispatcher.createActionBar()
- .recipient(event.player)
+ .viewer(event.player)
.template(Component.text("This is an action bar message!"))
.dispatch()
@@ -119,7 +119,7 @@ player.createActionBar()
.dispatch()
AdventureMessageDispatcher.createTitle()
- .recipient(event.player)
+ .viewer(event.player)
.title { it.template(Component.text("Hello!")) }
.subtitle { it.template(Component.text("It's great to see you!")) }
.times(2, 4, 2)
@@ -128,7 +128,7 @@ AdventureMessageDispatcher.createTitle()
### Synchronous vs asynchronous message dispatching
- [dispatch](https://github.com/rchomczyk/honey/tree/main/honey-common/src/dev/shiza/honey/message/dispatcher/MessageBaseDispatcher.java#L71)
- This method immediately delivers the message synchronously. It calls the deliver function with the rendered message and the recipient, and the action is completed immediately.
+ This method immediately delivers the message synchronously. It calls the deliver function with the rendered message and the viewer, and the action is completed immediately.
- [dispatchAsync](https://github.com/rchomczyk/honey/tree/main/honey-common/src/dev/shiza/honey/message/dispatcher/MessageBaseDispatcher.java#L76)
This method delivers the message asynchronously. It returns a CompletableFuture that performs the message rendering in the background and then delivers the result once it's ready. It allows non-blocking behavior and handles exceptions asynchronously.
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/honey-publish.gradle.kts b/buildSrc/src/main/kotlin/honey-publish.gradle.kts
index a7fea66..a73fd68 100644
--- a/buildSrc/src/main/kotlin/honey-publish.gradle.kts
+++ b/buildSrc/src/main/kotlin/honey-publish.gradle.kts
@@ -4,7 +4,7 @@ plugins {
}
group = "dev.shiza"
-version = "2.0.3-SNAPSHOT"
+version = "2.1.0-SNAPSHOT"
java {
withSourcesJar()
diff --git a/honey-common/src/dev/shiza/honey/adventure/message/compiler/AdventureMessageCompiler.java b/honey-common/src/dev/shiza/honey/adventure/message/compiler/AdventureMessageCompiler.java
index 98d998b..e374b3c 100644
--- a/honey-common/src/dev/shiza/honey/adventure/message/compiler/AdventureMessageCompiler.java
+++ b/honey-common/src/dev/shiza/honey/adventure/message/compiler/AdventureMessageCompiler.java
@@ -6,7 +6,7 @@
import dev.shiza.honey.adventure.placeholder.ParsableValue;
import dev.shiza.honey.message.compiler.MessageCompiler;
-import dev.shiza.honey.placeholder.sanitizer.SanitizedPlaceholder;
+import dev.shiza.honey.placeholder.sanitizer.PlaceholderSanitizer.SanitizedPlaceholder;
import java.util.List;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
@@ -46,12 +46,9 @@ public Component compile(
* @return An array of {@link TagResolver}.
*/
private TagResolver[] getPlaceholderTags(final List placeholders) {
- final TagResolver[] tagResolvers = new TagResolver[placeholders.size()];
- for (int index = 0; index < tagResolvers.length; index++) {
- final SanitizedPlaceholder placeholder = placeholders.get(index);
- tagResolvers[index] = getPlaceholderTags(placeholder.key(), placeholder.evaluatedValue());
- }
- return tagResolvers;
+ return placeholders.stream()
+ .map(placeholder -> getPlaceholderTags(placeholder.key(), placeholder.evaluatedValue()))
+ .toArray(TagResolver[]::new);
}
/**
diff --git a/honey-common/src/dev/shiza/honey/adventure/message/dispatcher/AdventureTitleMessageDispatcher.java b/honey-common/src/dev/shiza/honey/adventure/message/dispatcher/AdventureTitleMessageDispatcher.java
index c7a08e1..01d2472 100644
--- a/honey-common/src/dev/shiza/honey/adventure/message/dispatcher/AdventureTitleMessageDispatcher.java
+++ b/honey-common/src/dev/shiza/honey/adventure/message/dispatcher/AdventureTitleMessageDispatcher.java
@@ -4,6 +4,7 @@
import com.spotify.futures.CompletableFutures;
import dev.shiza.honey.message.dispatcher.MessageBaseDispatcher;
import dev.shiza.honey.message.dispatcher.MessageDispatcher;
+import dev.shiza.honey.message.dispatcher.MessageRenderer;
import dev.shiza.honey.message.dispatcher.TitleMessageDispatcher;
import java.time.Duration;
import java.util.List;
@@ -18,7 +19,7 @@
/**
* This class is responsible for dispatching title messages to an audience in the Adventure
* framework. It manages separate dispatchers for times, title, and subtitle, as well as keeping
- * track of the recipient.
+ * track of the viewer.
*/
public final class AdventureTitleMessageDispatcher
implements TitleMessageDispatcher {
@@ -26,31 +27,31 @@ public final class AdventureTitleMessageDispatcher
private final MessageDispatcher times;
private final MessageDispatcher title;
private final MessageDispatcher subtitle;
- private final Audience recipient;
+ private final Audience viewer;
/**
* Constructs a new AdventureTitleMessageDispatcher with specified dispatchers for times, title,
- * subtitle and a recipient.
+ * subtitle and a viewer.
*
* @param times the dispatcher for handling the timing of title messages
* @param title the dispatcher for handling the main title messages
* @param subtitle the dispatcher for handling the subtitle messages
- * @param recipient the audience that will receive the title messages
+ * @param viewer the audience that will receive the title messages
*/
public AdventureTitleMessageDispatcher(
final MessageDispatcher times,
final MessageDispatcher title,
final MessageDispatcher subtitle,
- final Audience recipient) {
+ final Audience viewer) {
this.times = times;
this.title = title;
this.subtitle = subtitle;
- this.recipient = recipient;
+ this.viewer = viewer;
}
/**
* Default constructor that initializes with default dispatchers for times, title, and subtitle,
- * with an empty recipient.
+ * with an empty viewer.
*/
public AdventureTitleMessageDispatcher() {
this(
@@ -82,9 +83,9 @@ public TitleMessageDispatcher times(
Duration.ofSeconds(fadeIn), Duration.ofSeconds(stay), Duration.ofSeconds(fadeOut));
final MessageDispatcher timesDispatcher =
new MessageBaseDispatcher<>(
- recipient, (audience, component) -> audience.sendTitlePart(TitlePart.TIMES, titleTime));
+ viewer, (audience, component) -> audience.sendTitlePart(TitlePart.TIMES, titleTime));
return new AdventureTitleMessageDispatcher(
- timesDispatcher.template(Component.empty()), title, subtitle, recipient);
+ timesDispatcher.template(Component.empty()), title, subtitle, viewer);
}
/**
@@ -96,7 +97,7 @@ public TitleMessageDispatcher times(
@Override
public TitleMessageDispatcher title(
final UnaryOperator> consumer) {
- return new AdventureTitleMessageDispatcher(times, consumer.apply(title), subtitle, recipient);
+ return new AdventureTitleMessageDispatcher(times, consumer.apply(title), subtitle, viewer);
}
/**
@@ -108,30 +109,40 @@ public TitleMessageDispatcher title(
@Override
public TitleMessageDispatcher subtitle(
final UnaryOperator> consumer) {
- return new AdventureTitleMessageDispatcher(times, title, consumer.apply(subtitle), recipient);
+ return new AdventureTitleMessageDispatcher(times, title, consumer.apply(subtitle), viewer);
+ }
+
+ @Override
+ public TitleMessageDispatcher placeholders(
+ final UnaryOperator> consumer) {
+ return new AdventureTitleMessageDispatcher(
+ times.placeholders(consumer),
+ title.placeholders(consumer),
+ subtitle.placeholders(consumer),
+ viewer);
}
/**
- * Updates the recipient of title messages.
+ * Updates the viewer of title messages.
*
- * @param recipient the new recipient for the title messages
- * @return a new instance of AdventureTitleMessageDispatcher with the updated recipient
+ * @param viewer the new viewer for the title messages
+ * @return a new instance of AdventureTitleMessageDispatcher with the updated viewer
*/
@Override
- public TitleMessageDispatcher recipient(final Audience recipient) {
- return new AdventureTitleMessageDispatcher(times, title, subtitle, recipient);
+ public TitleMessageDispatcher viewer(final Audience viewer) {
+ return new AdventureTitleMessageDispatcher(times, title, subtitle, viewer);
}
- /** Dispatches the title, subtitle, and timing messages to the recipient synchronously. */
+ /** Dispatches the title, subtitle, and timing messages to the viewer synchronously. */
@Override
public void dispatch() {
- times.recipient(recipient).dispatch();
- title.recipient(recipient).dispatch();
- subtitle.recipient(recipient).dispatch();
+ times.viewer(viewer).dispatch();
+ title.viewer(viewer).dispatch();
+ subtitle.viewer(viewer).dispatch();
}
/**
- * Dispatches the title, subtitle, and timing messages to the recipient asynchronously.
+ * Dispatches the title, subtitle, and timing messages to the viewer asynchronously.
*
* @return a CompletableFuture representing pending completion of the task, with a list of Void,
* indicating when all dispatches are complete
@@ -140,8 +151,8 @@ public void dispatch() {
public CompletableFuture> dispatchAsync() {
return CompletableFutures.allAsList(
ImmutableList.of(
- times.recipient(recipient).dispatchAsync(),
- title.recipient(recipient).dispatchAsync(),
- subtitle.recipient(recipient).dispatchAsync()));
+ times.viewer(viewer).dispatchAsync(),
+ title.viewer(viewer).dispatchAsync(),
+ subtitle.viewer(viewer).dispatchAsync()));
}
}
diff --git a/honey-common/src/dev/shiza/honey/adventure/message/formatter/AdventureMessageFormatter.java b/honey-common/src/dev/shiza/honey/adventure/message/formatter/AdventureMessageFormatter.java
index 3c34858..4191b54 100644
--- a/honey-common/src/dev/shiza/honey/adventure/message/formatter/AdventureMessageFormatter.java
+++ b/honey-common/src/dev/shiza/honey/adventure/message/formatter/AdventureMessageFormatter.java
@@ -2,7 +2,7 @@
import dev.shiza.honey.message.compiler.MessageCompiler;
import dev.shiza.honey.message.formatter.MessageBaseFormatter;
-import dev.shiza.honey.placeholder.evaluator.PlaceholderContext;
+import dev.shiza.honey.placeholder.PlaceholderContext;
import dev.shiza.honey.placeholder.processor.PlaceholderProcessor;
import dev.shiza.honey.placeholder.resolver.PlaceholderResolver;
import dev.shiza.honey.placeholder.sanitizer.PlaceholderSanitizer;
diff --git a/honey-common/src/dev/shiza/honey/adventure/message/formatter/AdventureMessageFormatterFactory.java b/honey-common/src/dev/shiza/honey/adventure/message/formatter/AdventureMessageFormatterFactory.java
index 773ec9b..be07945 100644
--- a/honey-common/src/dev/shiza/honey/adventure/message/formatter/AdventureMessageFormatterFactory.java
+++ b/honey-common/src/dev/shiza/honey/adventure/message/formatter/AdventureMessageFormatterFactory.java
@@ -1,9 +1,10 @@
package dev.shiza.honey.adventure.message.formatter;
import dev.shiza.honey.adventure.message.compiler.AdventureMessageCompilerFactory;
+import dev.shiza.honey.adventure.placeholder.sanitizer.AdventurePlaceholderSanitizerFactory;
import dev.shiza.honey.conversion.ImplicitConversion;
import dev.shiza.honey.message.compiler.MessageCompiler;
-import dev.shiza.honey.placeholder.evaluator.PlaceholderContext;
+import dev.shiza.honey.placeholder.PlaceholderContext;
import dev.shiza.honey.placeholder.evaluator.PlaceholderEvaluator;
import dev.shiza.honey.placeholder.evaluator.reflection.ReflectivePlaceholderEvaluatorFactory;
import dev.shiza.honey.placeholder.processor.PlaceholderProcessor;
@@ -11,7 +12,6 @@
import dev.shiza.honey.placeholder.resolver.PlaceholderResolver;
import dev.shiza.honey.placeholder.resolver.PlaceholderResolverFactory;
import dev.shiza.honey.placeholder.sanitizer.PlaceholderSanitizer;
-import dev.shiza.honey.placeholder.sanitizer.PlaceholderSanitizerFactory;
import dev.shiza.honey.processor.ProcessorRegistry;
import dev.shiza.honey.processor.ProcessorRegistryFactory;
import net.kyori.adventure.text.Component;
@@ -62,7 +62,7 @@ public static AdventureMessageFormatter create() {
final ImplicitConversion implicitConversion = ImplicitConversion.create();
final PlaceholderContext placeholderContext = PlaceholderContext.create();
final PlaceholderResolver placeholderResolver = PlaceholderResolverFactory.create();
- final PlaceholderSanitizer placeholderSanitizer = PlaceholderSanitizerFactory.create();
+ final PlaceholderSanitizer placeholderSanitizer = AdventurePlaceholderSanitizerFactory.create();
final PlaceholderEvaluator placeholderEvaluator =
ReflectivePlaceholderEvaluatorFactory.create();
final PlaceholderProcessor placeholderProcessor =
diff --git a/honey-common/src/dev/shiza/honey/adventure/placeholder/sanitizer/AdventurePlaceholderSanitizer.java b/honey-common/src/dev/shiza/honey/adventure/placeholder/sanitizer/AdventurePlaceholderSanitizer.java
new file mode 100644
index 0000000..758c726
--- /dev/null
+++ b/honey-common/src/dev/shiza/honey/adventure/placeholder/sanitizer/AdventurePlaceholderSanitizer.java
@@ -0,0 +1,42 @@
+package dev.shiza.honey.adventure.placeholder.sanitizer;
+
+import dev.shiza.honey.placeholder.evaluator.PlaceholderEvaluator.EvaluatedPlaceholder;
+import dev.shiza.honey.placeholder.sanitizer.PlaceholderSanitizer;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class is responsible for placeholder sanitization, which is the process of transforming
+ * placeholder key from honey's format to match the MiniMessage's format.
+ */
+final class AdventurePlaceholderSanitizer implements PlaceholderSanitizer {
+
+ private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{\\{(.*?)}}");
+ private static final String MINI_MESSAGE_PLACEHOLDER_FORMAT = "<%s>";
+
+ AdventurePlaceholderSanitizer() {}
+
+ @Override
+ public String getSanitizedContent(
+ final String content, final List placeholders) {
+ return processPlaceholders(content, MINI_MESSAGE_PLACEHOLDER_FORMAT);
+ }
+
+ @Override
+ public SanitizedPlaceholder getSanitizedPlaceholder(final EvaluatedPlaceholder placeholder) {
+ final String sanitized = processPlaceholders(placeholder.placeholder().key(), "%s");
+ return new SanitizedPlaceholder(
+ sanitized, placeholder.placeholder().key(), placeholder.evaluatedValue());
+ }
+
+ private String processPlaceholders(final String input, final String format) {
+ final Matcher matcher = PLACEHOLDER_PATTERN.matcher(input);
+ final StringBuilder result = new StringBuilder();
+ while (matcher.find()) {
+ matcher.appendReplacement(result, format.formatted(matcher.group(1)));
+ }
+ matcher.appendTail(result);
+ return result.toString();
+ }
+}
diff --git a/honey-common/src/dev/shiza/honey/adventure/placeholder/sanitizer/AdventurePlaceholderSanitizerFactory.java b/honey-common/src/dev/shiza/honey/adventure/placeholder/sanitizer/AdventurePlaceholderSanitizerFactory.java
new file mode 100644
index 0000000..a682224
--- /dev/null
+++ b/honey-common/src/dev/shiza/honey/adventure/placeholder/sanitizer/AdventurePlaceholderSanitizerFactory.java
@@ -0,0 +1,16 @@
+package dev.shiza.honey.adventure.placeholder.sanitizer;
+
+import dev.shiza.honey.placeholder.sanitizer.PlaceholderSanitizer;
+
+public final class AdventurePlaceholderSanitizerFactory {
+
+ private AdventurePlaceholderSanitizerFactory() {}
+
+ public static PlaceholderSanitizer create() {
+ return new AdventurePlaceholderSanitizer();
+ }
+
+ public static PlaceholderSanitizer createReflective() {
+ return new AdventureReflectivePlaceholderSanitizer();
+ }
+}
diff --git a/honey-common/src/dev/shiza/honey/adventure/placeholder/sanitizer/AdventureReflectivePlaceholderSanitizer.java b/honey-common/src/dev/shiza/honey/adventure/placeholder/sanitizer/AdventureReflectivePlaceholderSanitizer.java
new file mode 100644
index 0000000..3857792
--- /dev/null
+++ b/honey-common/src/dev/shiza/honey/adventure/placeholder/sanitizer/AdventureReflectivePlaceholderSanitizer.java
@@ -0,0 +1,92 @@
+package dev.shiza.honey.adventure.placeholder.sanitizer;
+
+import dev.shiza.honey.placeholder.evaluator.PlaceholderEvaluator.EvaluatedPlaceholder;
+import dev.shiza.honey.placeholder.sanitizer.PlaceholderSanitizationException;
+import dev.shiza.honey.placeholder.sanitizer.PlaceholderSanitizer;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class is responsible for placeholder sanitization before passing it to MiniMessage, ensuring
+ * that the passed placeholder keys are valid within theirs tag pattern. Unfortunately, due to the
+ * nature of honey, which allows for high flexibility in placeholders, as well as writing a lot of
+ * reflective calls the sanitization has to be done in a way that would cover most of the cases.
+ */
+final class AdventureReflectivePlaceholderSanitizer implements PlaceholderSanitizer {
+
+ private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{\\{([^}]+)}}");
+ private static final Pattern METHOD_CALL_PATTERN = Pattern.compile("\\b\\w+(?:\\.\\w+)*");
+ private static final Pattern TAG_PATTERN = Pattern.compile("[!?#]?[a-z0-9_-]*");
+ private static final Pattern DOT_PATTERN = Pattern.compile("\\.");
+ private static final Pattern PARANTHESE_PATTERN = Pattern.compile("\\(\\)");
+ private static final Pattern ALPHANUMERICAL_PATTERN = Pattern.compile("\\W");
+ private static final Pattern TRANSFORMATION_PATTERN = Pattern.compile("[^a-z0-9_-]");
+ private static final String MINI_MESSAGE_PLACEHOLDER_FORMAT = "<%s>";
+
+ private static final AdventureReflectiveTransformationPipeline TRANSFORMATION_PIPELINE =
+ new AdventureReflectiveTransformationPipeline(
+ List.of(
+ path -> PARANTHESE_PATTERN.matcher(path).replaceAll(""),
+ path -> DOT_PATTERN.matcher(path).replaceAll(""),
+ path -> ALPHANUMERICAL_PATTERN.matcher(path).replaceAll(""),
+ path -> path.toLowerCase(Locale.ROOT),
+ path -> TRANSFORMATION_PATTERN.matcher(path).replaceAll("")));
+
+ AdventureReflectivePlaceholderSanitizer() {}
+
+ @Override
+ public String getSanitizedContent(
+ final String content, final List placeholders) {
+ return placeholders.stream()
+ .reduce(
+ content,
+ (sanitized, placeholder) ->
+ sanitized.replace(
+ placeholder.expression(),
+ MINI_MESSAGE_PLACEHOLDER_FORMAT.formatted(placeholder.key())),
+ (a, b) -> b);
+ }
+
+ @Override
+ public SanitizedPlaceholder getSanitizedPlaceholder(final EvaluatedPlaceholder placeholder) {
+ final String expression =
+ extractPlaceholderExpression(placeholder.placeholder().key())
+ .orElseThrow(
+ () ->
+ new PlaceholderSanitizationException(
+ "Could not sanitize placeholder with key: %s"
+ .formatted(placeholder.placeholder().key())));
+
+ final String sanitizedExpression = getSanitizedExpression(expression);
+ if (!TAG_PATTERN.matcher(sanitizedExpression).matches()) {
+ throw new PlaceholderSanitizationException(
+ "Sanitized expression does not match tag pattern: %s".formatted(sanitizedExpression));
+ }
+
+ return new SanitizedPlaceholder(
+ sanitizedExpression, placeholder.placeholder().key(), placeholder.evaluatedValue());
+ }
+
+ private Optional extractPlaceholderExpression(final String key) {
+ final Matcher matcher = PLACEHOLDER_PATTERN.matcher(key);
+ return matcher.find() ? Optional.of(matcher.group(1)) : Optional.empty();
+ }
+
+ private String getSanitizedExpression(final String expression) {
+ final Matcher methodCallMatcher = METHOD_CALL_PATTERN.matcher(expression);
+ final StringBuilder result = new StringBuilder();
+ int lastEnd = 0;
+
+ while (methodCallMatcher.find()) {
+ result.append(expression, lastEnd, methodCallMatcher.start());
+ result.append(TRANSFORMATION_PIPELINE.apply(methodCallMatcher.group()));
+ lastEnd = methodCallMatcher.end();
+ }
+
+ result.append(expression.substring(lastEnd));
+ return TRANSFORMATION_PIPELINE.apply(result.toString());
+ }
+}
diff --git a/honey-common/src/dev/shiza/honey/adventure/placeholder/sanitizer/AdventureReflectiveTransformationPipeline.java b/honey-common/src/dev/shiza/honey/adventure/placeholder/sanitizer/AdventureReflectiveTransformationPipeline.java
new file mode 100644
index 0000000..63e8611
--- /dev/null
+++ b/honey-common/src/dev/shiza/honey/adventure/placeholder/sanitizer/AdventureReflectiveTransformationPipeline.java
@@ -0,0 +1,19 @@
+package dev.shiza.honey.adventure.placeholder.sanitizer;
+
+import java.util.List;
+import java.util.function.Function;
+
+final class AdventureReflectiveTransformationPipeline {
+
+ private final List> transformations;
+
+ AdventureReflectiveTransformationPipeline(final List> transformations) {
+ this.transformations = transformations;
+ }
+
+ public String apply(final String input) {
+ return transformations.stream()
+ .reduce(Function.identity(), Function::andThen)
+ .apply(input);
+ }
+}
\ No newline at end of file
diff --git a/honey-common/src/dev/shiza/honey/message/Message.java b/honey-common/src/dev/shiza/honey/message/Message.java
index 86d6cb8..399b299 100644
--- a/honey-common/src/dev/shiza/honey/message/Message.java
+++ b/honey-common/src/dev/shiza/honey/message/Message.java
@@ -1,6 +1,6 @@
package dev.shiza.honey.message;
-import dev.shiza.honey.placeholder.evaluator.PlaceholderContext;
+import dev.shiza.honey.placeholder.PlaceholderContext;
import java.util.function.UnaryOperator;
/**
@@ -35,10 +35,10 @@ public static Message blank() {
* Applies a {@code UnaryOperator} that modifies the placeholder context, and returns a new {@code
* Message} instance with the modified context.
*
- * @param mutator the function that modifies the placeholder context
+ * @param consumer the function that modifies the placeholder context
* @return a new {@code Message} instance with the modified placeholder context
*/
- public Message placeholders(final UnaryOperator mutator) {
- return new Message(content, mutator.apply(context));
+ public Message placeholders(final UnaryOperator consumer) {
+ return new Message(content, consumer.apply(context));
}
}
diff --git a/honey-common/src/dev/shiza/honey/message/compiler/MessageCompiler.java b/honey-common/src/dev/shiza/honey/message/compiler/MessageCompiler.java
index d6ba64e..fc634e3 100644
--- a/honey-common/src/dev/shiza/honey/message/compiler/MessageCompiler.java
+++ b/honey-common/src/dev/shiza/honey/message/compiler/MessageCompiler.java
@@ -1,6 +1,6 @@
package dev.shiza.honey.message.compiler;
-import dev.shiza.honey.placeholder.sanitizer.SanitizedPlaceholder;
+import dev.shiza.honey.placeholder.sanitizer.PlaceholderSanitizer.SanitizedPlaceholder;
import java.util.List;
/**
diff --git a/honey-common/src/dev/shiza/honey/message/dispatcher/MessageBaseDispatcher.java b/honey-common/src/dev/shiza/honey/message/dispatcher/MessageBaseDispatcher.java
index 299cb3b..bc99a82 100644
--- a/honey-common/src/dev/shiza/honey/message/dispatcher/MessageBaseDispatcher.java
+++ b/honey-common/src/dev/shiza/honey/message/dispatcher/MessageBaseDispatcher.java
@@ -7,13 +7,14 @@
import dev.shiza.honey.message.formatter.MessageFormatter;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
+import java.util.function.UnaryOperator;
import org.jetbrains.annotations.ApiStatus.Internal;
/**
* Internal class representing a base dispatcher for messages.
* This class is marked as final and cannot be subclassed.
*
-* @param the type of recipient for the message
+* @param the type of viewer for the message
* @param the type of message result
*/
@Internal
@@ -21,31 +22,31 @@ public final class MessageBaseDispatcher
implements MessageDispatcher {
private final MessageRenderer renderer;
- private final VIEWER recipient;
+ private final VIEWER viewer;
private final BiConsumer deliver;
private MessageBaseDispatcher(
final MessageRenderer renderer,
- final VIEWER recipient,
+ final VIEWER viewer,
final BiConsumer deliver) {
this.renderer = renderer;
- this.recipient = recipient;
+ this.viewer = viewer;
this.deliver = deliver;
}
- public MessageBaseDispatcher(final VIEWER recipient, final BiConsumer deliver) {
- this(new EmptyMessageRenderer<>(), recipient, deliver);
+ public MessageBaseDispatcher(final VIEWER viewer, final BiConsumer deliver) {
+ this(new EmptyMessageRenderer<>(), viewer, deliver);
}
/**
- * Replaces the current recipient with a new one and returns a new dispatcher instance.
+ * Replaces the current viewer with a new one and returns a new dispatcher instance.
*
- * @param recipient the new recipient of the message
- * @return a new instance of MessageBaseDispatcher with the updated recipient
+ * @param viewer the new viewer of the message
+ * @return a new instance of MessageBaseDispatcher with the updated viewer
*/
@Override
- public MessageDispatcher recipient(final VIEWER recipient) {
- return new MessageBaseDispatcher<>(renderer, recipient, deliver);
+ public MessageDispatcher viewer(final VIEWER viewer) {
+ return new MessageBaseDispatcher<>(renderer, viewer, deliver);
}
/**
@@ -59,7 +60,7 @@ public MessageDispatcher recipient(final VIEWER recipient) {
public MessageDispatcher template(
final MessageFormatter formatter, final String template) {
return new MessageBaseDispatcher<>(
- new FormattingMessageRenderer<>(formatter, Message.of(template)), recipient, deliver);
+ new FormattingMessageRenderer<>(formatter, Message.of(template)), viewer, deliver);
}
/**
@@ -71,61 +72,25 @@ public MessageDispatcher template(
@Override
public MessageDispatcher template(final RESULT message) {
return new MessageBaseDispatcher<>(
- new DelegatingMessageRenderer<>(message), recipient, deliver);
+ new DelegatingMessageRenderer<>(message), viewer, deliver);
}
- /**
- * Adds a variable to the message template and returns a new dispatcher instance.
- *
- * @param key the variable key
- * @param value the variable value
- * @return a new instance of MessageBaseDispatcher with the variable added
- */
- @Override
- public MessageDispatcher variable(final String key, final Object value) {
- final MessageRenderer newRenderer = renderer.variable(key, value);
- return new MessageBaseDispatcher<>(newRenderer, recipient, deliver);
- }
-
- /**
- * Adds a promised variable to the message template and returns a new dispatcher instance.
- *
- * @param key the variable key
- * @param value the promised variable value
- * @return a new instance of MessageBaseDispatcher with the promised variable added
- */
- @Override
- public MessageDispatcher promisedVariable(final String key, final Object value) {
- final MessageRenderer newRenderer = renderer.promisedVariable(key, value);
- return new MessageBaseDispatcher<>(newRenderer, recipient, deliver);
- }
-
- /**
- * Adds a promised variable with a CompletableFuture to the message template
- * and returns a new dispatcher instance.
- *
- * @param key the variable key
- * @param value the CompletableFuture object representing the variable value
- * @return a new instance of MessageBaseDispatcher with the promised variable added
- */
@Override
- public MessageDispatcher promisedVariable(
- final String key, final CompletableFuture