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 value) { - final MessageRenderer newRenderer = renderer.promisedVariable(key, value); - return new MessageBaseDispatcher<>(newRenderer, recipient, deliver); + public MessageDispatcher placeholders( + final UnaryOperator> consumer) { + return new MessageBaseDispatcher<>(consumer.apply(renderer), viewer, deliver); } - /** - * Dispatches the rendered message to the recipient. + * Dispatches the rendered message to the viewer. */ @Override public void dispatch() { - deliver.accept(recipient, renderer.render()); + deliver.accept(viewer, renderer.render()); } /** - * Asynchronously dispatches the rendered message to the recipient. + * Asynchronously dispatches the rendered message to the viewer. * * @return a CompletableFuture that completes when the message is dispatched * @throws MessageDispatchingException if an unexpected exception occurs during dispatch @@ -134,7 +99,7 @@ public void dispatch() { public CompletableFuture dispatchAsync() { return renderer .renderAsync() - .thenAccept(result -> deliver.accept(recipient, result)) + .thenAccept(result -> deliver.accept(viewer, result)) .exceptionally(cause -> { throw new MessageDispatchingException( "Could not dispatch message, because of unexpected exception.", cause); diff --git a/honey-common/src/dev/shiza/honey/message/dispatcher/MessageDispatcher.java b/honey-common/src/dev/shiza/honey/message/dispatcher/MessageDispatcher.java index 03ca977..2d49469 100644 --- a/honey-common/src/dev/shiza/honey/message/dispatcher/MessageDispatcher.java +++ b/honey-common/src/dev/shiza/honey/message/dispatcher/MessageDispatcher.java @@ -2,23 +2,24 @@ import dev.shiza.honey.message.formatter.MessageFormatter; import java.util.concurrent.CompletableFuture; +import java.util.function.UnaryOperator; /** * The {@code MessageDispatcher} interface defines methods for configuring and dispatching messages - * to a recipient. + * to a viewer. * - * @param the type of the recipient to whom the message will be sent + * @param the type of the viewer to whom the message will be sent * @param the type of the result or content of the message */ public interface MessageDispatcher { /** - * Sets the recipient of the message. + * Sets the viewer of the message. * - * @param recipient the recipient of the message + * @param viewer the viewer of the message * @return the current instance of {@code MessageDispatcher} for method chaining */ - MessageDispatcher recipient(final VIEWER recipient); + MessageDispatcher viewer(final VIEWER viewer); /** * Sets the message template and formatter for the message. @@ -38,33 +39,7 @@ MessageDispatcher template( */ MessageDispatcher template(final RESULT message); - /** - * Adds a variable to the message context. - * - * @param key the key of the placeholder - * @param value the value of the placeholder - * @return the current instance of {@code MessageDispatcher} for method chaining - */ - MessageDispatcher variable(final String key, final Object value); - - /** - * Adds a promised variable to the message context. The value is provided asynchronously. - * - * @param key the key for the variable - * @param value the value of the variable - * @return the current instance of {@code MessageDispatcher} for method chaining - */ - MessageDispatcher promisedVariable(final String key, final Object value); - - /** - * Adds a promised variable to the message context. The value is provided asynchronously. - * - * @param key the key for the variable - * @param value the future object containing the value of the variable - * @return the current instance of {@code MessageDispatcher} for method chaining - */ - MessageDispatcher promisedVariable( - final String key, final CompletableFuture value); + MessageDispatcher placeholders(final UnaryOperator> consumer); /** Dispatches the message synchronously. */ void dispatch(); diff --git a/honey-common/src/dev/shiza/honey/message/dispatcher/MessageRenderer.java b/honey-common/src/dev/shiza/honey/message/dispatcher/MessageRenderer.java index d91c902..5fc8c88 100644 --- a/honey-common/src/dev/shiza/honey/message/dispatcher/MessageRenderer.java +++ b/honey-common/src/dev/shiza/honey/message/dispatcher/MessageRenderer.java @@ -3,8 +3,8 @@ import com.spotify.futures.CompletableFutures; import dev.shiza.honey.message.Message; import dev.shiza.honey.message.dispatcher.MessageRenderer.DelegatingMessageRenderer; -import dev.shiza.honey.message.dispatcher.MessageRenderer.FormattingMessageRenderer; import dev.shiza.honey.message.dispatcher.MessageRenderer.EmptyMessageRenderer; +import dev.shiza.honey.message.dispatcher.MessageRenderer.FormattingMessageRenderer; import dev.shiza.honey.message.formatter.MessageFormatter; import java.util.concurrent.CompletableFuture; @@ -22,40 +22,24 @@ public sealed interface MessageRenderer * * @param key the key for the variable * @param value the value to associate with the key - * @return a new instance of a message renderer with the variable added - * @throws MessageDispatchingException if the operation is not supported + * @return a new instance of a message renderer with the variable added if supported or same + * instance */ - default MessageRenderer variable(final String key, final Object value) { - throw new MessageDispatchingException( - "Cannot add variable to a non-formatting message renderer"); + default MessageRenderer replace(final String key, final Object value) { + return this; } /** - * Adds a promised variable. This method may not be supported by non-formatting renderers. - * - * @param key the key for the variable - * @param value the value to associate with the key - * @return a new instance of a message renderer with the variable added - * @throws MessageDispatchingException if the operation is not supported - */ - default MessageRenderer promisedVariable(final String key, final Object value) { - throw new MessageDispatchingException( - "Cannot add promised variable to a non-formatting message renderer"); - } - - /** - * Adds a promised variable to the message for asynchronous resolutions. This method may not be - * supported by non-formatting renderers. + * Adds an asynchronous variable to the message for asynchronous resolutions. This method may not + * be supported by non-formatting renderers. * * @param key the key for the variable * @param value the future promise of the value to associate with the key - * @return a new instance of a message renderer with the promised variable added - * @throws MessageDispatchingException if the operation is not supported + * @return a new instance of a message renderer with the variable added if supported or same + * instance */ - default MessageRenderer promisedVariable( - final String key, final CompletableFuture value) { - throw new MessageDispatchingException( - "Cannot add promised variable to a non-formatting message renderer"); + default MessageRenderer replace(final String key, final CompletableFuture value) { + return this; } /** @@ -94,7 +78,7 @@ public CompletableFuture renderAsync() { /** * A formatting message renderer that uses a specified message formatter and message to render - * output. Supports dynamic addition of variables and promises. + * output. Supports dynamic addition of variables and asynchronous variables. * * @param the type of the result of rendering * @param formatter the formatter to format the message @@ -104,21 +88,16 @@ record FormattingMessageRenderer(MessageFormatter formatter, Mes implements MessageRenderer { @Override - public MessageRenderer variable(String key, Object value) { + public MessageRenderer replace(final String key, final Object value) { return new FormattingMessageRenderer<>( formatter, message.placeholders(it -> it.withValue(key, value))); } @Override - public MessageRenderer promisedVariable(String key, Object value) { - return new FormattingMessageRenderer<>( - formatter, message.placeholders(it -> it.withPromisedValue(key, value))); - } - - @Override - public MessageRenderer promisedVariable(String key, CompletableFuture value) { + public MessageRenderer replace( + final String key, final CompletableFuture value) { return new FormattingMessageRenderer<>( - formatter, message.placeholders(it -> it.withPromisedValue(key, value))); + formatter, message.placeholders(it -> it.withAsynchronousValue(key, value))); } @Override diff --git a/honey-common/src/dev/shiza/honey/message/dispatcher/TitleMessageDispatcher.java b/honey-common/src/dev/shiza/honey/message/dispatcher/TitleMessageDispatcher.java index 86e9820..05f40bd 100644 --- a/honey-common/src/dev/shiza/honey/message/dispatcher/TitleMessageDispatcher.java +++ b/honey-common/src/dev/shiza/honey/message/dispatcher/TitleMessageDispatcher.java @@ -6,9 +6,9 @@ /** * Interface for a TitleMessageDispatcher which is responsible for sending titles, subtitles, and - * managing their display times to specific recipients. + * managing their display times to specific viewers. * - * @param The type of the viewer/recipient for whom the messages are intended. + * @param The type of the viewer for whom the messages are intended. * @param The result type for operations that are performed by the MessageDispatcher. */ public interface TitleMessageDispatcher { @@ -41,19 +41,22 @@ TitleMessageDispatcher title( TitleMessageDispatcher subtitle( final UnaryOperator> consumer); + TitleMessageDispatcher placeholders( + final UnaryOperator> consumer); + /** - * Sets the recipient of the title message. + * Sets the viewer of the title message. * - * @param recipient The viewer/recipient who will receive the message. + * @param viewer The viewer who will receive the message. * @return An instance of TitleMessageDispatcher for method chaining. */ - TitleMessageDispatcher recipient(final VIEWER recipient); + TitleMessageDispatcher viewer(final VIEWER viewer); - /** Dispatches the title and subtitle to the recipient synchronously. */ + /** Dispatches the title and subtitle to the viewer synchronously. */ void dispatch(); /** - * Dispatches the title and subtitle to the recipient asynchronously. + * Dispatches the title and subtitle to the viewer asynchronously. * * @return A CompletableFuture that completes with a list of results (normally empty) once the * message dispatch operation is complete. diff --git a/honey-common/src/dev/shiza/honey/message/formatter/MessageBaseFormatter.java b/honey-common/src/dev/shiza/honey/message/formatter/MessageBaseFormatter.java index d638fc7..523a1a8 100644 --- a/honey-common/src/dev/shiza/honey/message/formatter/MessageBaseFormatter.java +++ b/honey-common/src/dev/shiza/honey/message/formatter/MessageBaseFormatter.java @@ -2,12 +2,12 @@ import dev.shiza.honey.message.Message; 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.processor.PlaceholderProcessor; import dev.shiza.honey.placeholder.resolver.Placeholder; import dev.shiza.honey.placeholder.resolver.PlaceholderResolver; import dev.shiza.honey.placeholder.sanitizer.PlaceholderSanitizer; -import dev.shiza.honey.placeholder.sanitizer.SanitizedPlaceholder; +import dev.shiza.honey.placeholder.sanitizer.PlaceholderSanitizer.SanitizedPlaceholder; import dev.shiza.honey.processor.ProcessorRegistry; import java.util.List; import java.util.Set; diff --git a/honey-common/src/dev/shiza/honey/placeholder/evaluator/PlaceholderContext.java b/honey-common/src/dev/shiza/honey/placeholder/PlaceholderContext.java similarity index 59% rename from honey-common/src/dev/shiza/honey/placeholder/evaluator/PlaceholderContext.java rename to honey-common/src/dev/shiza/honey/placeholder/PlaceholderContext.java index 1694ee6..3395d8d 100644 --- a/honey-common/src/dev/shiza/honey/placeholder/evaluator/PlaceholderContext.java +++ b/honey-common/src/dev/shiza/honey/placeholder/PlaceholderContext.java @@ -1,4 +1,4 @@ -package dev.shiza.honey.placeholder.evaluator; +package dev.shiza.honey.placeholder; import com.google.common.collect.ImmutableMap; import java.util.Map; @@ -7,12 +7,12 @@ public final class PlaceholderContext { private final Map values; - private final Map> promisedValues; + private final Map> asynchronousValues; private PlaceholderContext( - final Map values, final Map> promisedValues) { + final Map values, final Map> asynchronousValues) { this.values = ImmutableMap.copyOf(values); - this.promisedValues = ImmutableMap.copyOf(promisedValues); + this.asynchronousValues = ImmutableMap.copyOf(asynchronousValues); } public static PlaceholderContext create() { @@ -22,24 +22,15 @@ public static PlaceholderContext create() { public PlaceholderContext withValue(final String valueName, final Object value) { return new PlaceholderContext( ImmutableMap.builder().putAll(values).put(valueName, value).build(), - promisedValues); + asynchronousValues); } - public PlaceholderContext withPromisedValue(final String valueName, final Object value) { - return new PlaceholderContext( - values, - ImmutableMap.>builder() - .putAll(promisedValues) - .put(valueName, CompletableFuture.completedFuture(value)) - .build()); - } - - public PlaceholderContext withPromisedValue( + public PlaceholderContext withAsynchronousValue( final String valueName, final CompletableFuture value) { return new PlaceholderContext( values, ImmutableMap.>builder() - .putAll(promisedValues) + .putAll(asynchronousValues) .put(valueName, value) .build()); } @@ -52,20 +43,20 @@ public Map getValues() { return values; } - public CompletableFuture getPromisedValue(final String valueName) { - return promisedValues.get(valueName); + public CompletableFuture getAsynchronousValue(final String valueName) { + return asynchronousValues.get(valueName); } - public Map> getPromisedValues() { - return promisedValues; + public Map> getAsynchronousValues() { + return asynchronousValues; } public PlaceholderContext merge(final PlaceholderContext context) { return new PlaceholderContext( ImmutableMap.builder().putAll(values).putAll(context.getValues()).build(), ImmutableMap.>builder() - .putAll(promisedValues) - .putAll(context.getPromisedValues()) + .putAll(asynchronousValues) + .putAll(context.getAsynchronousValues()) .build()); } } diff --git a/honey-common/src/dev/shiza/honey/placeholder/evaluator/EvaluatedPlaceholder.java b/honey-common/src/dev/shiza/honey/placeholder/evaluator/EvaluatedPlaceholder.java deleted file mode 100644 index db031f1..0000000 --- a/honey-common/src/dev/shiza/honey/placeholder/evaluator/EvaluatedPlaceholder.java +++ /dev/null @@ -1,5 +0,0 @@ -package dev.shiza.honey.placeholder.evaluator; - -import dev.shiza.honey.placeholder.resolver.Placeholder; - -public record EvaluatedPlaceholder(Placeholder placeholder, Object evaluatedValue) {} diff --git a/honey-common/src/dev/shiza/honey/placeholder/evaluator/PlaceholderEvaluator.java b/honey-common/src/dev/shiza/honey/placeholder/evaluator/PlaceholderEvaluator.java index a50c50b..f16b40a 100644 --- a/honey-common/src/dev/shiza/honey/placeholder/evaluator/PlaceholderEvaluator.java +++ b/honey-common/src/dev/shiza/honey/placeholder/evaluator/PlaceholderEvaluator.java @@ -1,12 +1,14 @@ package dev.shiza.honey.placeholder.evaluator; +import dev.shiza.honey.placeholder.PlaceholderContext; +import dev.shiza.honey.placeholder.evaluator.visitor.PlaceholderVisitor; import dev.shiza.honey.placeholder.resolver.Placeholder; -import dev.shiza.honey.placeholder.visitor.PlaceholderVisitor; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.function.Supplier; +@FunctionalInterface public interface PlaceholderEvaluator { EvaluatedPlaceholder evaluate( @@ -24,4 +26,6 @@ default List evaluate( } return evaluatedPlaceholders; } + + record EvaluatedPlaceholder(Placeholder placeholder, Object evaluatedValue) {} } diff --git a/honey-common/src/dev/shiza/honey/placeholder/evaluator/reflection/ReflectivePlaceholderEvaluator.java b/honey-common/src/dev/shiza/honey/placeholder/evaluator/reflection/ReflectivePlaceholderEvaluator.java index b0cbb67..2633b46 100644 --- a/honey-common/src/dev/shiza/honey/placeholder/evaluator/reflection/ReflectivePlaceholderEvaluator.java +++ b/honey-common/src/dev/shiza/honey/placeholder/evaluator/reflection/ReflectivePlaceholderEvaluator.java @@ -1,10 +1,9 @@ package dev.shiza.honey.placeholder.evaluator.reflection; -import dev.shiza.honey.placeholder.evaluator.EvaluatedPlaceholder; -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.visitor.PlaceholderVisitor; import dev.shiza.honey.placeholder.resolver.Placeholder; -import dev.shiza.honey.placeholder.visitor.PlaceholderVisitor; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.reflect.Method; diff --git a/honey-common/src/dev/shiza/honey/placeholder/evaluator/visitor/PlaceholderVisitingException.java b/honey-common/src/dev/shiza/honey/placeholder/evaluator/visitor/PlaceholderVisitingException.java new file mode 100644 index 0000000..16eed60 --- /dev/null +++ b/honey-common/src/dev/shiza/honey/placeholder/evaluator/visitor/PlaceholderVisitingException.java @@ -0,0 +1,8 @@ +package dev.shiza.honey.placeholder.evaluator.visitor; + +public final class PlaceholderVisitingException extends IllegalStateException { + + public PlaceholderVisitingException(final String message) { + super(message); + } +} diff --git a/honey-common/src/dev/shiza/honey/placeholder/evaluator/visitor/PlaceholderVisitor.java b/honey-common/src/dev/shiza/honey/placeholder/evaluator/visitor/PlaceholderVisitor.java new file mode 100644 index 0000000..43f0384 --- /dev/null +++ b/honey-common/src/dev/shiza/honey/placeholder/evaluator/visitor/PlaceholderVisitor.java @@ -0,0 +1,88 @@ +package dev.shiza.honey.placeholder.evaluator.visitor; + +import dev.shiza.honey.placeholder.PlaceholderContext; +import dev.shiza.honey.placeholder.evaluator.visitor.PlaceholderVisitor.AsynchronousPlaceholderVisitor; +import dev.shiza.honey.placeholder.evaluator.visitor.PlaceholderVisitor.SynchronousPlaceholderVisitor; +import dev.shiza.honey.placeholder.resolver.Placeholder; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiFunction; +import java.util.function.UnaryOperator; + +public sealed interface PlaceholderVisitor + permits AsynchronousPlaceholderVisitor, SynchronousPlaceholderVisitor { + + static PlaceholderVisitor synchronousVisitor() { + return new SynchronousPlaceholderVisitor(); + } + + static PlaceholderVisitor> asynchronousVisitor() { + return new AsynchronousPlaceholderVisitor(); + } + + void start(final Placeholder placeholder, final PlaceholderContext context, final String path); + + void visit(final UnaryOperator invocation); + + T complete(); + + default R resolveNonNullValue( + final Placeholder placeholder, + final PlaceholderContext context, + final String path, + final BiFunction valueResolver) { + return Optional.ofNullable(valueResolver.apply(context, path)) + .orElseThrow( + () -> + new PlaceholderVisitingException( + "Could not evaluate placeholder with key %s, because of unknown variable named %s" + .formatted(placeholder.key(), path))); + } + + final class SynchronousPlaceholderVisitor implements PlaceholderVisitor { + + private Object result; + + SynchronousPlaceholderVisitor() {} + + @Override + public void start( + final Placeholder placeholder, final PlaceholderContext context, final String path) { + result = resolveNonNullValue(placeholder, context, path, PlaceholderContext::getValue); + } + + @Override + public void visit(final UnaryOperator invocation) { + result = invocation.apply(result); + } + + @Override + public Object complete() { + return result; + } + } + + final class AsynchronousPlaceholderVisitor implements PlaceholderVisitor> { + + private CompletableFuture result; + + AsynchronousPlaceholderVisitor() {} + + @Override + public void start( + final Placeholder placeholder, final PlaceholderContext context, final String path) { + result = + resolveNonNullValue(placeholder, context, path, PlaceholderContext::getAsynchronousValue); + } + + @Override + public void visit(final UnaryOperator invocation) { + result = result.thenApply(invocation); + } + + @Override + public CompletableFuture complete() { + return result; + } + } +} diff --git a/honey-common/src/dev/shiza/honey/placeholder/processor/PlaceholderProcessor.java b/honey-common/src/dev/shiza/honey/placeholder/processor/PlaceholderProcessor.java index b2ebba7..6a6c776 100644 --- a/honey-common/src/dev/shiza/honey/placeholder/processor/PlaceholderProcessor.java +++ b/honey-common/src/dev/shiza/honey/placeholder/processor/PlaceholderProcessor.java @@ -1,8 +1,8 @@ package dev.shiza.honey.placeholder.processor; -import dev.shiza.honey.placeholder.evaluator.PlaceholderContext; +import dev.shiza.honey.placeholder.PlaceholderContext; import dev.shiza.honey.placeholder.resolver.Placeholder; -import dev.shiza.honey.placeholder.sanitizer.SanitizedPlaceholder; +import dev.shiza.honey.placeholder.sanitizer.PlaceholderSanitizer.SanitizedPlaceholder; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; diff --git a/honey-common/src/dev/shiza/honey/placeholder/processor/PlaceholderProcessorImpl.java b/honey-common/src/dev/shiza/honey/placeholder/processor/PlaceholderProcessorImpl.java index a58fca1..6e20582 100644 --- a/honey-common/src/dev/shiza/honey/placeholder/processor/PlaceholderProcessorImpl.java +++ b/honey-common/src/dev/shiza/honey/placeholder/processor/PlaceholderProcessorImpl.java @@ -2,14 +2,13 @@ import com.spotify.futures.CompletableFutures; import dev.shiza.honey.conversion.ImplicitConversion; -import dev.shiza.honey.placeholder.evaluator.EvaluatedPlaceholder; -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.PlaceholderEvaluator.EvaluatedPlaceholder; +import dev.shiza.honey.placeholder.evaluator.visitor.PlaceholderVisitor; import dev.shiza.honey.placeholder.resolver.Placeholder; import dev.shiza.honey.placeholder.sanitizer.PlaceholderSanitizer; -import dev.shiza.honey.placeholder.sanitizer.SanitizedPlaceholder; -import dev.shiza.honey.placeholder.visitor.PlaceholderVisitorImpl; -import dev.shiza.honey.placeholder.visitor.PromisingPlaceholderVisitor; +import dev.shiza.honey.placeholder.sanitizer.PlaceholderSanitizer.SanitizedPlaceholder; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -34,7 +33,7 @@ final class PlaceholderProcessorImpl implements PlaceholderProcessor { public List process( final PlaceholderContext context, final Set placeholders) { final List evaluatedPlaceholders = - placeholderEvaluator.evaluate(context, PlaceholderVisitorImpl::create, placeholders); + placeholderEvaluator.evaluate(context, PlaceholderVisitor::synchronousVisitor, placeholders); final List sanitizedPlaceholders = placeholderSanitizer.getSanitizedPlaceholders(evaluatedPlaceholders); return convertPlaceholders(sanitizedPlaceholders); @@ -44,7 +43,7 @@ public List process( public CompletableFuture> processAsync( final PlaceholderContext context, final Set placeholders) { final List evaluatedPlaceholders = - placeholderEvaluator.evaluate(context, PromisingPlaceholderVisitor::create, placeholders); + placeholderEvaluator.evaluate(context, PlaceholderVisitor::asynchronousVisitor, placeholders); return unwrapPromisedValues(evaluatedPlaceholders) .thenApply(placeholderSanitizer::getSanitizedPlaceholders) .thenApply(this::convertPlaceholders); diff --git a/honey-common/src/dev/shiza/honey/placeholder/resolver/PlaceholderResolver.java b/honey-common/src/dev/shiza/honey/placeholder/resolver/PlaceholderResolver.java index 5e23b33..63cbf58 100644 --- a/honey-common/src/dev/shiza/honey/placeholder/resolver/PlaceholderResolver.java +++ b/honey-common/src/dev/shiza/honey/placeholder/resolver/PlaceholderResolver.java @@ -2,6 +2,7 @@ import java.util.Set; +@FunctionalInterface public interface PlaceholderResolver { Set resolve(final String message); diff --git a/honey-common/src/dev/shiza/honey/placeholder/sanitizer/DelegatingPlaceholderSanitizer.java b/honey-common/src/dev/shiza/honey/placeholder/sanitizer/DelegatingPlaceholderSanitizer.java new file mode 100644 index 0000000..14d43d8 --- /dev/null +++ b/honey-common/src/dev/shiza/honey/placeholder/sanitizer/DelegatingPlaceholderSanitizer.java @@ -0,0 +1,27 @@ +package dev.shiza.honey.placeholder.sanitizer; + +import dev.shiza.honey.placeholder.evaluator.PlaceholderEvaluator.EvaluatedPlaceholder; +import java.util.List; + +/** + * A delegating placeholder sanitizer that does not perform any sanitization, + * but instead returns the original content and placeholders. Probably, it would + * be more efficient to use this sanitizer, when you are not making any complex + * reflection calls in placeholder expression. + */ +final class DelegatingPlaceholderSanitizer implements PlaceholderSanitizer { + + @Override + public String getSanitizedContent( + final String content, final List placeholders) { + return content; + } + + @Override + public SanitizedPlaceholder getSanitizedPlaceholder(final EvaluatedPlaceholder placeholder) { + return new SanitizedPlaceholder( + placeholder.placeholder().key(), + placeholder.placeholder().expression(), + placeholder.evaluatedValue()); + } +} diff --git a/honey-common/src/dev/shiza/honey/placeholder/sanitizer/DelegatingPlaceholderSanitizerFactory.java b/honey-common/src/dev/shiza/honey/placeholder/sanitizer/DelegatingPlaceholderSanitizerFactory.java new file mode 100644 index 0000000..c3281f3 --- /dev/null +++ b/honey-common/src/dev/shiza/honey/placeholder/sanitizer/DelegatingPlaceholderSanitizerFactory.java @@ -0,0 +1,10 @@ +package dev.shiza.honey.placeholder.sanitizer; + +public final class DelegatingPlaceholderSanitizerFactory { + + private DelegatingPlaceholderSanitizerFactory() {} + + public static PlaceholderSanitizer create() { + return new DelegatingPlaceholderSanitizer(); + } +} diff --git a/honey-common/src/dev/shiza/honey/placeholder/sanitizer/PlaceholderSanitizer.java b/honey-common/src/dev/shiza/honey/placeholder/sanitizer/PlaceholderSanitizer.java index 4319b86..6f7c2bb 100644 --- a/honey-common/src/dev/shiza/honey/placeholder/sanitizer/PlaceholderSanitizer.java +++ b/honey-common/src/dev/shiza/honey/placeholder/sanitizer/PlaceholderSanitizer.java @@ -1,6 +1,7 @@ package dev.shiza.honey.placeholder.sanitizer; -import dev.shiza.honey.placeholder.evaluator.EvaluatedPlaceholder; +import dev.shiza.honey.placeholder.evaluator.PlaceholderEvaluator.EvaluatedPlaceholder; +import java.util.ArrayList; import java.util.List; public interface PlaceholderSanitizer { @@ -9,6 +10,14 @@ public interface PlaceholderSanitizer { SanitizedPlaceholder getSanitizedPlaceholder(final EvaluatedPlaceholder placeholder); - List getSanitizedPlaceholders( - final List placeholders); + default List getSanitizedPlaceholders( + final List placeholders) { + final List sanitizedPlaceholders = new ArrayList<>(); + for (final EvaluatedPlaceholder placeholder : placeholders) { + sanitizedPlaceholders.add(getSanitizedPlaceholder(placeholder)); + } + return sanitizedPlaceholders; + } + + record SanitizedPlaceholder(String key, String expression, Object evaluatedValue) {} } diff --git a/honey-common/src/dev/shiza/honey/placeholder/sanitizer/PlaceholderSanitizerFactory.java b/honey-common/src/dev/shiza/honey/placeholder/sanitizer/PlaceholderSanitizerFactory.java deleted file mode 100644 index 405536d..0000000 --- a/honey-common/src/dev/shiza/honey/placeholder/sanitizer/PlaceholderSanitizerFactory.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.shiza.honey.placeholder.sanitizer; - -public final class PlaceholderSanitizerFactory { - - private PlaceholderSanitizerFactory() {} - - public static PlaceholderSanitizer create() { - return new PlaceholderSanitizerImpl(); - } -} diff --git a/honey-common/src/dev/shiza/honey/placeholder/sanitizer/PlaceholderSanitizerImpl.java b/honey-common/src/dev/shiza/honey/placeholder/sanitizer/PlaceholderSanitizerImpl.java deleted file mode 100644 index a826b53..0000000 --- a/honey-common/src/dev/shiza/honey/placeholder/sanitizer/PlaceholderSanitizerImpl.java +++ /dev/null @@ -1,88 +0,0 @@ -package dev.shiza.honey.placeholder.sanitizer; - -import dev.shiza.honey.placeholder.evaluator.EvaluatedPlaceholder; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -final class PlaceholderSanitizerImpl 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 char TAG_RESOLVER_INIT = '<'; - private static final char TAG_RESOLVER_STOP = '>'; - - PlaceholderSanitizerImpl() {} - - @Override - public String getSanitizedContent( - final String content, final List placeholders) { - String sanitizedContent = content; - for (final SanitizedPlaceholder placeholder : placeholders) { - sanitizedContent = - sanitizedContent.replace(placeholder.expression(), getResolvableTag(placeholder.key())); - } - return sanitizedContent; - } - - @Override - public SanitizedPlaceholder getSanitizedPlaceholder(final EvaluatedPlaceholder placeholder) { - final Matcher matcher = PLACEHOLDER_PATTERN.matcher(placeholder.placeholder().key()); - while (matcher.find()) { - final String expression = matcher.group(1); - final String sanitizedExpression = getSanitizedExpression(expression); - if (TAG_PATTERN.matcher(sanitizedExpression).matches()) { - return new SanitizedPlaceholder( - sanitizedExpression, placeholder.placeholder().key(), placeholder.evaluatedValue()); - } - } - - throw new PlaceholderSanitizationException( - "Could not sanitize placeholder with key: %s".formatted(placeholder.placeholder().key())); - } - - @Override - public List getSanitizedPlaceholders( - final List placeholders) { - final List sanitizedPlaceholders = new ArrayList<>(); - for (final EvaluatedPlaceholder placeholder : placeholders) { - sanitizedPlaceholders.add(getSanitizedPlaceholder(placeholder)); - } - return sanitizedPlaceholders; - } - - private String getSanitizedExpression(final String expression) { - int lastEnd = 0; - - final StringBuilder result = new StringBuilder(); - final Matcher methodCallMatcher = METHOD_CALL_PATTERN.matcher(expression); - while (methodCallMatcher.find()) { - result.append(expression, lastEnd, methodCallMatcher.start()); - String transformedMethodCall = methodCallMatcher.group(); - transformedMethodCall = PARANTHESE_PATTERN.matcher(transformedMethodCall).replaceAll(""); - transformedMethodCall = DOT_PATTERN.matcher(transformedMethodCall).replaceAll(""); - transformedMethodCall = ALPHANUMERICAL_PATTERN.matcher(transformedMethodCall).replaceAll(""); - transformedMethodCall = transformedMethodCall.toLowerCase(Locale.ROOT); - result.append(transformedMethodCall); - - lastEnd = methodCallMatcher.end(); - } - result.append(expression.substring(lastEnd)); - - String transformedExpression = result.toString(); - transformedExpression = TRANSFORMATION_PATTERN.matcher(transformedExpression).replaceAll(""); - transformedExpression = transformedExpression.toLowerCase(Locale.ROOT); - return transformedExpression; - } - - private String getResolvableTag(final String key) { - return TAG_RESOLVER_INIT + key + TAG_RESOLVER_STOP; - } -} diff --git a/honey-common/src/dev/shiza/honey/placeholder/sanitizer/SanitizedPlaceholder.java b/honey-common/src/dev/shiza/honey/placeholder/sanitizer/SanitizedPlaceholder.java deleted file mode 100644 index b1f7568..0000000 --- a/honey-common/src/dev/shiza/honey/placeholder/sanitizer/SanitizedPlaceholder.java +++ /dev/null @@ -1,3 +0,0 @@ -package dev.shiza.honey.placeholder.sanitizer; - -public record SanitizedPlaceholder(String key, String expression, Object evaluatedValue) {} diff --git a/honey-common/src/dev/shiza/honey/placeholder/visitor/PlaceholderVisitor.java b/honey-common/src/dev/shiza/honey/placeholder/visitor/PlaceholderVisitor.java deleted file mode 100644 index 3cf0634..0000000 --- a/honey-common/src/dev/shiza/honey/placeholder/visitor/PlaceholderVisitor.java +++ /dev/null @@ -1,14 +0,0 @@ -package dev.shiza.honey.placeholder.visitor; - -import dev.shiza.honey.placeholder.evaluator.PlaceholderContext; -import dev.shiza.honey.placeholder.resolver.Placeholder; -import java.util.function.UnaryOperator; - -public interface PlaceholderVisitor { - - void start(final Placeholder placeholder, final PlaceholderContext context, final String path); - - void visit(final UnaryOperator invocation); - - T complete(); -} diff --git a/honey-common/src/dev/shiza/honey/placeholder/visitor/PlaceholderVisitorImpl.java b/honey-common/src/dev/shiza/honey/placeholder/visitor/PlaceholderVisitorImpl.java deleted file mode 100644 index 250aa67..0000000 --- a/honey-common/src/dev/shiza/honey/placeholder/visitor/PlaceholderVisitorImpl.java +++ /dev/null @@ -1,38 +0,0 @@ -package dev.shiza.honey.placeholder.visitor; - -import dev.shiza.honey.placeholder.evaluator.PlaceholderContext; -import dev.shiza.honey.placeholder.evaluator.reflection.ReflectivePlaceholderEvaluationException; -import dev.shiza.honey.placeholder.resolver.Placeholder; -import java.util.function.UnaryOperator; - -public final class PlaceholderVisitorImpl implements PlaceholderVisitor { - - private Object current; - - private PlaceholderVisitorImpl() {} - - public static PlaceholderVisitorImpl create() { - return new PlaceholderVisitorImpl(); - } - - @Override - public void start( - final Placeholder placeholder, final PlaceholderContext context, final String path) { - current = context.getValue(path); - if (current == null) { - throw new ReflectivePlaceholderEvaluationException( - "Could not evaluate placeholder with key %s, because of unknown variable named %s" - .formatted(placeholder.key(), path)); - } - } - - @Override - public void visit(final UnaryOperator invocation) { - current = invocation.apply(current); - } - - @Override - public Object complete() { - return current; - } -} diff --git a/honey-common/src/dev/shiza/honey/placeholder/visitor/PromisingPlaceholderVisitor.java b/honey-common/src/dev/shiza/honey/placeholder/visitor/PromisingPlaceholderVisitor.java deleted file mode 100644 index cd2006c..0000000 --- a/honey-common/src/dev/shiza/honey/placeholder/visitor/PromisingPlaceholderVisitor.java +++ /dev/null @@ -1,39 +0,0 @@ -package dev.shiza.honey.placeholder.visitor; - -import dev.shiza.honey.placeholder.evaluator.PlaceholderContext; -import dev.shiza.honey.placeholder.evaluator.reflection.ReflectivePlaceholderEvaluationException; -import dev.shiza.honey.placeholder.resolver.Placeholder; -import java.util.concurrent.CompletableFuture; -import java.util.function.UnaryOperator; - -public final class PromisingPlaceholderVisitor implements PlaceholderVisitor> { - - private CompletableFuture current; - - private PromisingPlaceholderVisitor() {} - - public static PromisingPlaceholderVisitor create() { - return new PromisingPlaceholderVisitor(); - } - - @Override - public void start( - final Placeholder placeholder, final PlaceholderContext context, final String path) { - current = context.getPromisedValue(path); - if (current == null) { - throw new ReflectivePlaceholderEvaluationException( - "Could not evaluate placeholder with key %s, because of unknown promised variable named %s" - .formatted(placeholder.key(), path)); - } - } - - @Override - public void visit(final UnaryOperator invocation) { - current = current.thenApply(invocation); - } - - @Override - public CompletableFuture complete() { - return current; - } -} diff --git a/honey-common/test/dev/shiza/honey/placeholder/adventure/placeholder/sanitizer/AdventurePlaceholderSanitizerTest.java b/honey-common/test/dev/shiza/honey/placeholder/adventure/placeholder/sanitizer/AdventurePlaceholderSanitizerTest.java new file mode 100644 index 0000000..d33ded8 --- /dev/null +++ b/honey-common/test/dev/shiza/honey/placeholder/adventure/placeholder/sanitizer/AdventurePlaceholderSanitizerTest.java @@ -0,0 +1,46 @@ +package dev.shiza.honey.placeholder.adventure.placeholder.sanitizer; + +import static dev.shiza.honey.placeholder.adventure.placeholder.sanitizer.AdventurePlaceholderSanitizerTestUtils.SANITIZER; +import static dev.shiza.honey.placeholder.adventure.placeholder.sanitizer.AdventurePlaceholderSanitizerTestUtils.placeholder; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Test; + +final class AdventurePlaceholderSanitizerTest { + + @Test + void sanitizationWithoutPlaceholdersShouldNotAffectInput() { + assertThat(SANITIZER.getSanitizedContent("Hello world!", Collections.emptyList())) + .isEqualTo("Hello world!"); + } + + @Test + void sanitizationWithoutPlaceholdersShouldNotAffectInputWhenBracketsNotClosed() { + assertThat(SANITIZER.getSanitizedContent("Hello {{world!", Collections.emptyList())) + .isEqualTo("Hello {{world!"); + assertThat(SANITIZER.getSanitizedContent("Hello world!}}", Collections.emptyList())) + .isEqualTo("Hello world!}}"); + } + + @Test + void sanitizationWithSinglePlaceholderWithDirectValue() { + assertThat( + SANITIZER.getSanitizedContent( + "Hello {{user}}!", + Collections.singletonList(SANITIZER.getSanitizedPlaceholder(placeholder("user"))))) + .isEqualTo("Hello !"); + } + + @Test + void sanitizationWithMultiplePlaceholdersWithDirectValues() { + assertThat( + SANITIZER.getSanitizedContent( + "Hello {{user}}, your balance is {{balance}}!", + List.of( + SANITIZER.getSanitizedPlaceholder(placeholder("user")), + SANITIZER.getSanitizedPlaceholder(placeholder("balance"))))) + .isEqualTo("Hello , your balance is !"); + } +} diff --git a/honey-common/test/dev/shiza/honey/placeholder/adventure/placeholder/sanitizer/AdventurePlaceholderSanitizerTestUtils.java b/honey-common/test/dev/shiza/honey/placeholder/adventure/placeholder/sanitizer/AdventurePlaceholderSanitizerTestUtils.java new file mode 100644 index 0000000..4935bba --- /dev/null +++ b/honey-common/test/dev/shiza/honey/placeholder/adventure/placeholder/sanitizer/AdventurePlaceholderSanitizerTestUtils.java @@ -0,0 +1,19 @@ +package dev.shiza.honey.placeholder.adventure.placeholder.sanitizer; + +import dev.shiza.honey.adventure.placeholder.sanitizer.AdventurePlaceholderSanitizerFactory; +import dev.shiza.honey.placeholder.evaluator.PlaceholderEvaluator.EvaluatedPlaceholder; +import dev.shiza.honey.placeholder.resolver.Placeholder; +import dev.shiza.honey.placeholder.sanitizer.PlaceholderSanitizer; + +final class AdventurePlaceholderSanitizerTestUtils { + + static final PlaceholderSanitizer SANITIZER = AdventurePlaceholderSanitizerFactory.create(); + static final PlaceholderSanitizer REFLECTIVE_SANITIZER = + AdventurePlaceholderSanitizerFactory.createReflective(); + + private AdventurePlaceholderSanitizerTestUtils() {} + + static EvaluatedPlaceholder placeholder(final String expression) { + return new EvaluatedPlaceholder(new Placeholder("{{" + expression + "}}", expression), null); + } +} diff --git a/honey-common/test/dev/shiza/honey/placeholder/sanitizer/PlaceholderSanitizerImplTest.java b/honey-common/test/dev/shiza/honey/placeholder/adventure/placeholder/sanitizer/AdventureReflectivePlaceholderSanitizerTest.java similarity index 75% rename from honey-common/test/dev/shiza/honey/placeholder/sanitizer/PlaceholderSanitizerImplTest.java rename to honey-common/test/dev/shiza/honey/placeholder/adventure/placeholder/sanitizer/AdventureReflectivePlaceholderSanitizerTest.java index cbf9de1..36603e8 100644 --- a/honey-common/test/dev/shiza/honey/placeholder/sanitizer/PlaceholderSanitizerImplTest.java +++ b/honey-common/test/dev/shiza/honey/placeholder/adventure/placeholder/sanitizer/AdventureReflectivePlaceholderSanitizerTest.java @@ -1,14 +1,15 @@ -package dev.shiza.honey.placeholder.sanitizer; +package dev.shiza.honey.placeholder.adventure.placeholder.sanitizer; -import static dev.shiza.honey.placeholder.sanitizer.PlaceholderSanitizerImplTestUtils.SANITIZER; -import static dev.shiza.honey.placeholder.sanitizer.PlaceholderSanitizerImplTestUtils.placeholder; +import static dev.shiza.honey.placeholder.adventure.placeholder.sanitizer.AdventurePlaceholderSanitizerTestUtils.REFLECTIVE_SANITIZER; +import static dev.shiza.honey.placeholder.adventure.placeholder.sanitizer.AdventurePlaceholderSanitizerTestUtils.placeholder; import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; +import dev.shiza.honey.placeholder.sanitizer.PlaceholderSanitizer.SanitizedPlaceholder; import java.util.List; import org.junit.jupiter.api.Test; -class PlaceholderSanitizerImplTest { +final class AdventureReflectivePlaceholderSanitizerTest { @Test void sanitizeContentWithoutPlaceholders() { @@ -59,7 +60,7 @@ void sanitizeSinglePlaceholderWithDoubleChild() { @Test void sanitizeBunchOfPlaceholders() { assertThat( - SANITIZER.getSanitizedPlaceholders( + REFLECTIVE_SANITIZER.getSanitizedPlaceholders( List.of( placeholder("user"), placeholder("user.test"), placeholder("user.test.name")))) .extracting(SanitizedPlaceholder::key) @@ -69,17 +70,17 @@ void sanitizeBunchOfPlaceholders() { private void assertContentSanitization( final String content, final List expressions, final String expectedValue) { assertThat( - SANITIZER.getSanitizedContent( + REFLECTIVE_SANITIZER.getSanitizedContent( content, - SANITIZER.getSanitizedPlaceholders( + REFLECTIVE_SANITIZER.getSanitizedPlaceholders( expressions.stream() - .map(PlaceholderSanitizerImplTestUtils::placeholder) + .map(AdventurePlaceholderSanitizerTestUtils::placeholder) .toList()))) .isEqualTo(expectedValue); } private void assertPlaceholderSanitization(final String expression, final String expectedValue) { - assertThat(SANITIZER.getSanitizedPlaceholder(placeholder(expression))) + assertThat(REFLECTIVE_SANITIZER.getSanitizedPlaceholder(placeholder(expression))) .extracting(SanitizedPlaceholder::key) .isEqualTo(expectedValue); } diff --git a/honey-common/test/dev/shiza/honey/placeholder/evaluator/PlaceholderContextTests.java b/honey-common/test/dev/shiza/honey/placeholder/evaluator/PlaceholderContextTest.java similarity index 63% rename from honey-common/test/dev/shiza/honey/placeholder/evaluator/PlaceholderContextTests.java rename to honey-common/test/dev/shiza/honey/placeholder/evaluator/PlaceholderContextTest.java index cc21b89..e86ff3c 100644 --- a/honey-common/test/dev/shiza/honey/placeholder/evaluator/PlaceholderContextTests.java +++ b/honey-common/test/dev/shiza/honey/placeholder/evaluator/PlaceholderContextTest.java @@ -2,20 +2,26 @@ import static org.assertj.core.api.Assertions.assertThat; +import dev.shiza.honey.placeholder.PlaceholderContext; +import java.util.concurrent.CompletableFuture; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; -class PlaceholderContextTests { +final class PlaceholderContextTest { @Test void mergeContexts() { final PlaceholderContext one = - PlaceholderContext.create().withValue("hello", "world").withPromisedValue("hello", "world"); + PlaceholderContext.create() + .withValue("hello", "world") + .withAsynchronousValue("hello", CompletableFuture.completedFuture("world")); final PlaceholderContext two = - PlaceholderContext.create().withValue("world", "hello").withPromisedValue("world", "hello"); + PlaceholderContext.create() + .withValue("world", "hello") + .withAsynchronousValue("world", CompletableFuture.completedFuture("hello")); final PlaceholderContext mergedContext = one.merge(two); assertThat(mergedContext.getValues()).hasSize(2); - assertThat(mergedContext.getPromisedValues()).hasSize(2); + assertThat(mergedContext.getAsynchronousValues()).hasSize(2); } @Test @@ -31,6 +37,6 @@ void mergeEmptyContexts() { final PlaceholderContext two = PlaceholderContext.create(); final PlaceholderContext mergedContext = one.merge(two); assertThat(mergedContext.getValues()).isEmpty(); - assertThat(mergedContext.getPromisedValues()).isEmpty(); + assertThat(mergedContext.getAsynchronousValues()).isEmpty(); } } diff --git a/honey-common/test/dev/shiza/honey/placeholder/evaluator/reflection/ReflectivePlaceholderEvaluatorTest.java b/honey-common/test/dev/shiza/honey/placeholder/evaluator/reflection/ReflectivePlaceholderEvaluatorTest.java index 0db6bea..bec7a36 100644 --- a/honey-common/test/dev/shiza/honey/placeholder/evaluator/reflection/ReflectivePlaceholderEvaluatorTest.java +++ b/honey-common/test/dev/shiza/honey/placeholder/evaluator/reflection/ReflectivePlaceholderEvaluatorTest.java @@ -11,22 +11,24 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; -import dev.shiza.honey.placeholder.evaluator.EvaluatedPlaceholder; -import dev.shiza.honey.placeholder.visitor.PlaceholderVisitorImpl; -import dev.shiza.honey.placeholder.visitor.PromisingPlaceholderVisitor; +import dev.shiza.honey.placeholder.evaluator.PlaceholderEvaluator.EvaluatedPlaceholder; +import dev.shiza.honey.placeholder.evaluator.visitor.PlaceholderVisitingException; +import dev.shiza.honey.placeholder.evaluator.visitor.PlaceholderVisitor; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import org.junit.jupiter.api.Test; -class ReflectivePlaceholderEvaluatorTest { +final class ReflectivePlaceholderEvaluatorTest { @Test void syncEvaluationToUnknownValue() { assertThatCode( () -> EVALUATOR.evaluate( - SYNC_CONTEXT, PlaceholderVisitorImpl.create(), placeholder("unknown.value"))) - .isInstanceOf(ReflectivePlaceholderEvaluationException.class) + SYNC_CONTEXT, + PlaceholderVisitor.synchronousVisitor(), + placeholder("unknown.value"))) + .isInstanceOf(PlaceholderVisitingException.class) .hasMessageMatching( "Could not evaluate placeholder with key \\{\\{(.*?)}}, because of unknown variable named (\\w+)"); } @@ -36,7 +38,9 @@ void syncEvaluationToUnknownChild() { assertThatCode( () -> EVALUATOR.evaluate( - SYNC_CONTEXT, PlaceholderVisitorImpl.create(), placeholder("user.test"))) + SYNC_CONTEXT, + PlaceholderVisitor.synchronousVisitor(), + placeholder("user.test"))) .isInstanceOf(ReflectivePlaceholderEvaluationException.class) .hasMessageMatching( "Could not get method handle for (\\w+) parent with (\\w+) path, because of unexpected exception."); @@ -59,7 +63,8 @@ void syncEvaluationToDoubleChild() { private void assertSyncEvaluationEqualTo(final String expression, final Object expectedValue) { final EvaluatedPlaceholder evaluation = - EVALUATOR.evaluate(SYNC_CONTEXT, PlaceholderVisitorImpl.create(), placeholder(expression)); + EVALUATOR.evaluate( + SYNC_CONTEXT, PlaceholderVisitor.synchronousVisitor(), placeholder(expression)); assertThat(evaluation.evaluatedValue()).isEqualTo(expectedValue); } @@ -68,16 +73,19 @@ void asyncEvaluationToUnknownValue() { assertThatCode( () -> EVALUATOR.evaluate( - ASYNC_CONTEXT, PromisingPlaceholderVisitor.create(), placeholder("unknown.value"))) - .isInstanceOf(ReflectivePlaceholderEvaluationException.class) + ASYNC_CONTEXT, + PlaceholderVisitor.asynchronousVisitor(), + placeholder("unknown.value"))) + .isInstanceOf(PlaceholderVisitingException.class) .hasMessageMatching( - "Could not evaluate placeholder with key \\{\\{(.*?)}}, because of unknown promised variable named (\\w+)"); + "Could not evaluate placeholder with key \\{\\{(.*?)}}, because of unknown variable named (\\w+)"); } @Test void asyncEvaluationToUnknownChild() { final EvaluatedPlaceholder evaluation = - EVALUATOR.evaluate(ASYNC_CONTEXT, PromisingPlaceholderVisitor.create(), placeholder("user.test")); + EVALUATOR.evaluate( + ASYNC_CONTEXT, PlaceholderVisitor.asynchronousVisitor(), placeholder("user.test")); assertThat((CompletableFuture) evaluation.evaluatedValue()) .isCompletedExceptionally() .failsWithin(INVOCATION_TIMEOUT) @@ -104,7 +112,8 @@ void asyncEvaluationToDoubleChild() { private void assertAsyncEvaluationEqualTo(final String expression, final Object expectedValue) { final EvaluatedPlaceholder evaluation = - EVALUATOR.evaluate(ASYNC_CONTEXT, PromisingPlaceholderVisitor.create(), placeholder(expression)); + EVALUATOR.evaluate( + ASYNC_CONTEXT, PlaceholderVisitor.asynchronousVisitor(), placeholder(expression)); assertThat((CompletableFuture) evaluation.evaluatedValue()) .isCompletedWithValueMatching(evaluatedValue -> evaluatedValue.equals(expectedValue)); } diff --git a/honey-common/test/dev/shiza/honey/placeholder/evaluator/reflection/ReflectivePlaceholderEvaluatorTestUtils.java b/honey-common/test/dev/shiza/honey/placeholder/evaluator/reflection/ReflectivePlaceholderEvaluatorTestUtils.java index d37ff40..ae1add1 100644 --- a/honey-common/test/dev/shiza/honey/placeholder/evaluator/reflection/ReflectivePlaceholderEvaluatorTestUtils.java +++ b/honey-common/test/dev/shiza/honey/placeholder/evaluator/reflection/ReflectivePlaceholderEvaluatorTestUtils.java @@ -2,12 +2,13 @@ import static java.time.Duration.ofSeconds; -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.ReflectivePlaceholderEvaluatorTest.Account; import dev.shiza.honey.placeholder.evaluator.reflection.ReflectivePlaceholderEvaluatorTest.User; import dev.shiza.honey.placeholder.resolver.Placeholder; import java.time.Duration; +import java.util.concurrent.CompletableFuture; final class ReflectivePlaceholderEvaluatorTestUtils { @@ -22,8 +23,8 @@ final class ReflectivePlaceholderEvaluatorTestUtils { PlaceholderContext.create().withValue("account", ACCOUNT).withValue("user", USER); static final PlaceholderContext ASYNC_CONTEXT = PlaceholderContext.create() - .withPromisedValue("account", ACCOUNT) - .withPromisedValue("user", USER); + .withAsynchronousValue("account", CompletableFuture.completedFuture(ACCOUNT)) + .withAsynchronousValue("user", CompletableFuture.completedFuture(USER)); static final PlaceholderEvaluator EVALUATOR = new ReflectivePlaceholderEvaluator(); static Placeholder placeholder(final String expression) { diff --git a/honey-common/test/dev/shiza/honey/placeholder/resolver/RegexPlaceholderResolverTest.java b/honey-common/test/dev/shiza/honey/placeholder/resolver/RegexPlaceholderResolverTest.java index f3a8c94..171bb09 100644 --- a/honey-common/test/dev/shiza/honey/placeholder/resolver/RegexPlaceholderResolverTest.java +++ b/honey-common/test/dev/shiza/honey/placeholder/resolver/RegexPlaceholderResolverTest.java @@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test; -class RegexPlaceholderResolverTest { +final class RegexPlaceholderResolverTest { @Test void resolveWithoutPlaceholders() { diff --git a/honey-common/test/dev/shiza/honey/placeholder/sanitizer/PlaceholderSanitizerImplTestUtils.java b/honey-common/test/dev/shiza/honey/placeholder/sanitizer/PlaceholderSanitizerImplTestUtils.java deleted file mode 100644 index 58cee52..0000000 --- a/honey-common/test/dev/shiza/honey/placeholder/sanitizer/PlaceholderSanitizerImplTestUtils.java +++ /dev/null @@ -1,15 +0,0 @@ -package dev.shiza.honey.placeholder.sanitizer; - -import dev.shiza.honey.placeholder.evaluator.EvaluatedPlaceholder; -import dev.shiza.honey.placeholder.resolver.Placeholder; - -final class PlaceholderSanitizerImplTestUtils { - - static final PlaceholderSanitizer SANITIZER = new PlaceholderSanitizerImpl(); - - private PlaceholderSanitizerImplTestUtils() {} - - static EvaluatedPlaceholder placeholder(final String expression) { - return new EvaluatedPlaceholder(new Placeholder("{{" + expression + "}}", expression), null); - } -} diff --git a/honey-kt-extension/src/dev/shiza/honey/title.kt b/honey-kt-extension/src/dev/shiza/honey/title.kt index 596cb61..829d24f 100644 --- a/honey-kt-extension/src/dev/shiza/honey/title.kt +++ b/honey-kt-extension/src/dev/shiza/honey/title.kt @@ -9,7 +9,7 @@ fun times(fadeIn: Int, stay: Int, fadeOut: Int): Triple = Triple(fadeIn, stay, fadeOut) fun AdventureMessageDispatcher.createTitle( - recipient: Audience, + viewer: Audience, titleConfig: MessageConfigurer, subtitleConfig: MessageConfigurer, fadeIn: Int, @@ -17,7 +17,7 @@ fun AdventureMessageDispatcher.createTitle( fadeOut: Int ): TitleMessageDispatcher = AdventureMessageDispatcher.createTitle() - .recipient(recipient) + .viewer(viewer) .title { base -> base.apply(titleConfig) } .subtitle { base -> base.apply(subtitleConfig) } .times(fadeIn, stay, fadeOut) @@ -30,7 +30,7 @@ fun Audience.createTitle( fadeOut: Int ): TitleMessageDispatcher = AdventureMessageDispatcher.createTitle() - .recipient(this) + .viewer(this) .title { base -> base.apply(titleConfig) } .subtitle { base -> base.apply(subtitleConfig) } .times(fadeIn, stay, fadeOut) @@ -43,7 +43,7 @@ fun Player.createTitle( fadeOut: Int ): TitleMessageDispatcher = AdventureMessageDispatcher.createTitle() - .recipient(this) + .viewer(this) .title { base -> base.apply(titleConfig) } .subtitle { base -> base.apply(subtitleConfig) } .times(fadeIn, stay, fadeOut) diff --git a/honey-test-plugin/src/dev/shiza/honey/ExampleListener.java b/honey-test-plugin/src/dev/shiza/honey/ExampleListener.java index c7b90a1..38a0ace 100644 --- a/honey-test-plugin/src/dev/shiza/honey/ExampleListener.java +++ b/honey-test-plugin/src/dev/shiza/honey/ExampleListener.java @@ -13,30 +13,31 @@ final class ExampleListener implements Listener { private final AdventureMessageFormatter formatter; - ExampleListener( - final AdventureMessageFormatter formatter) { + ExampleListener(final AdventureMessageFormatter formatter) { this.formatter = formatter; } @EventHandler public void onPlayerJoin(final PlayerJoinEvent event) { AdventureMessageDispatcher.createTitle() - .recipient(event.getPlayer()) - .title(it -> it.template(formatter, "Hello!")) + .viewer(event.getPlayer()) + .times(2, 4, 2) + .title(it -> it.template(formatter, "Hello {{number}}!")) .subtitle( it -> - it.template(formatter, "It is a pleasure to see you there {{player.getName}}") - .variable("player", event.getPlayer())) - .times(2, 4, 2) + it.template(formatter, "It is a pleasure to see you there {{number}} {{player}}") + .placeholders( + environment -> environment.replace("player", event.getPlayer().getName()))) + .placeholders(environment -> environment.replace("number", 15)) .dispatch(); 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(formatter, "Honey is great, isn't it?") .dispatch(); } diff --git a/settings.gradle.kts b/settings.gradle.kts index 6818302..678653f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,4 +3,4 @@ include(":honey-common") include(":honey-kt-extension") // Uncomment in case if you would like to run test plugin -// include(":honey-test-plugin") \ No newline at end of file +include(":honey-test-plugin") \ No newline at end of file