diff --git a/api/src/main/java/com/discordsrv/api/discord/api/entity/message/impl/SendableDiscordMessageImpl.java b/api/src/main/java/com/discordsrv/api/discord/api/entity/message/impl/SendableDiscordMessageImpl.java index 6148806c..3191a537 100644 --- a/api/src/main/java/com/discordsrv/api/discord/api/entity/message/impl/SendableDiscordMessageImpl.java +++ b/api/src/main/java/com/discordsrv/api/discord/api/entity/message/impl/SendableDiscordMessageImpl.java @@ -30,8 +30,8 @@ import com.discordsrv.api.discord.api.entity.message.DiscordMessageEmbed; import com.discordsrv.api.discord.api.entity.message.SendableDiscordMessage; import com.discordsrv.api.discord.api.util.DiscordFormattingUtil; import com.discordsrv.api.placeholder.FormattedText; -import com.discordsrv.api.placeholder.PlaceholderResultStringifier; import com.discordsrv.api.placeholder.PlaceholderService; +import com.discordsrv.api.placeholder.mapper.ResultMappers; import com.discordsrv.api.placeholder.util.Placeholders; import org.jetbrains.annotations.NotNull; @@ -259,7 +259,7 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage { DiscordMessageEmbed.Builder embedBuilder = embed.toBuilder(); // TODO: check which parts allow formatting more thoroughly - PlaceholderResultStringifier.plainComponents(() -> { + ResultMappers.runInPlainComponentContext(() -> { embedBuilder.setAuthor( placeholders.apply( embedBuilder.getAuthorName()), @@ -308,7 +308,7 @@ public class SendableDiscordMessageImpl implements SendableDiscordMessage { builder.addEmbed(embedBuilder.build()); } - PlaceholderResultStringifier.plainComponents(() -> { + ResultMappers.runInPlainComponentContext(() -> { builder.setWebhookUsername(placeholders.apply(builder.getWebhookUsername())); builder.setWebhookAvatarUrl(placeholders.apply(builder.getWebhookAvatarUrl())); }); diff --git a/api/src/main/java/com/discordsrv/api/event/events/placeholder/PlaceholderLookupEvent.java b/api/src/main/java/com/discordsrv/api/event/events/placeholder/PlaceholderLookupEvent.java index 4d21ca3e..69418a7e 100644 --- a/api/src/main/java/com/discordsrv/api/event/events/placeholder/PlaceholderLookupEvent.java +++ b/api/src/main/java/com/discordsrv/api/event/events/placeholder/PlaceholderLookupEvent.java @@ -28,27 +28,37 @@ import com.discordsrv.api.event.events.Processable; import com.discordsrv.api.placeholder.PlaceholderLookupResult; import org.jetbrains.annotations.NotNull; +import java.util.Optional; import java.util.Set; public class PlaceholderLookupEvent implements Event, Processable { private final String placeholder; - private final Set context; + private final Set contexts; private boolean processed; private PlaceholderLookupResult result; - public PlaceholderLookupEvent(String placeholder, Set context) { + public PlaceholderLookupEvent(String placeholder, Set contexts) { this.placeholder = placeholder; - this.context = context; + this.contexts = contexts; } public String getPlaceholder() { return placeholder; } - public Set getContext() { - return context; + public Set getContexts() { + return contexts; + } + + public Optional getContext(Class type) { + for (Object o : contexts) { + if (type.isAssignableFrom(o.getClass())) { + return Optional.of(o); + } + } + return Optional.empty(); } @Override @@ -70,7 +80,7 @@ public class PlaceholderLookupEvent implements Event, Processable { } /** - * Provides a {@link PlaceholderLookupResult} for the provided {@link #getPlaceholder()} and {@link #getContext()}. + * Provides a {@link PlaceholderLookupResult} for the provided {@link #getPlaceholder()} and {@link #getContexts()}. * @param result the result * @throws IllegalStateException if the event is already processed */ diff --git a/api/src/main/java/com/discordsrv/api/placeholder/PlaceholderService.java b/api/src/main/java/com/discordsrv/api/placeholder/PlaceholderService.java index bfe566ab..6d6557b2 100644 --- a/api/src/main/java/com/discordsrv/api/placeholder/PlaceholderService.java +++ b/api/src/main/java/com/discordsrv/api/placeholder/PlaceholderService.java @@ -23,6 +23,7 @@ package com.discordsrv.api.placeholder; +import com.discordsrv.api.placeholder.mapper.PlaceholderResultMapper; import org.jetbrains.annotations.NotNull; import java.util.Set; @@ -34,15 +35,15 @@ public interface PlaceholderService { /** * The primary pattern used by DiscordSRV to find placeholders. */ - Pattern PATTERN = Pattern.compile("(%)([^%]+)(%)"); + Pattern PATTERN = Pattern.compile("(%)((?:[^%]|(?<=\\\\)%)+)(%)"); /** * The pattern DiscordSRV uses to find recursive placeholders. */ - Pattern RECURSIVE_PATTERN = Pattern.compile("(\\{)(.+)(})"); + Pattern RECURSIVE_PATTERN = Pattern.compile("(\\{)((?:[^{}]|(?<=\\\\)[{}])+)(})"); - void addResultStringifier(@NotNull PlaceholderResultStringifier resultConverter); - void removeResultStringifier(@NotNull PlaceholderResultStringifier resultConverter); + void addResultMapper(@NotNull PlaceholderResultMapper resultMapper); + void removeResultMapper(@NotNull PlaceholderResultMapper resultMapper); String replacePlaceholders(@NotNull String placeholder, @NotNull Set context); String replacePlaceholders(@NotNull String placeholder, @NotNull Object... context); diff --git a/api/src/main/java/com/discordsrv/api/placeholder/PlaceholderResultStringifier.java b/api/src/main/java/com/discordsrv/api/placeholder/mapper/PlaceholderResultMapper.java similarity index 61% rename from api/src/main/java/com/discordsrv/api/placeholder/PlaceholderResultStringifier.java rename to api/src/main/java/com/discordsrv/api/placeholder/mapper/PlaceholderResultMapper.java index b28b24d4..689f269f 100644 --- a/api/src/main/java/com/discordsrv/api/placeholder/PlaceholderResultStringifier.java +++ b/api/src/main/java/com/discordsrv/api/placeholder/mapper/PlaceholderResultMapper.java @@ -21,37 +21,18 @@ * SOFTWARE. */ -package com.discordsrv.api.placeholder; +package com.discordsrv.api.placeholder.mapper; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @FunctionalInterface -public interface PlaceholderResultStringifier { - - /** - * @see #plainComponents(Runnable) - */ - @ApiStatus.Internal - ThreadLocal PLAIN_COMPONENT_CONTEXT = new ThreadLocal<>(); - - /** - * Utility method to run the provided {@link Runnable} where {@link PlaceholderService}s - * will replace {@link com.discordsrv.api.component.MinecraftComponent}s - * as plain without formatting (instead of converting to Discord formatting). - * @param runnable a task that will be executed immediately - */ - static void plainComponents(Runnable runnable) { - PLAIN_COMPONENT_CONTEXT.set(true); - runnable.run(); - PLAIN_COMPONENT_CONTEXT.set(false); - } +public interface PlaceholderResultMapper { /** * Converts a successful placeholder lookup result into a {@link String}. * @param result the result - * @return the result in {@link String} form + * @return the result in {@link String} form or {@code null} if this stringifier doesn't know what to do with this result */ - String convertPlaceholderResult(@NotNull Object result); + Object convertResult(@NotNull Object result); } diff --git a/api/src/main/java/com/discordsrv/api/placeholder/mapper/ResultMappers.java b/api/src/main/java/com/discordsrv/api/placeholder/mapper/ResultMappers.java new file mode 100644 index 00000000..5d744183 --- /dev/null +++ b/api/src/main/java/com/discordsrv/api/placeholder/mapper/ResultMappers.java @@ -0,0 +1,66 @@ +/* + * This file is part of the DiscordSRV API, licensed under the MIT License + * Copyright (c) 2016-2021 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.discordsrv.api.placeholder.mapper; + +import com.discordsrv.api.placeholder.PlaceholderService; + +import java.util.function.Supplier; + +public final class ResultMappers { + + private static final ThreadLocal PLAIN_COMPONENTS = new ThreadLocal<>(); + + private ResultMappers() {} + + public static boolean isPlainComponentContext() { + return PLAIN_COMPONENTS.get(); + } + + /** + * Utility method to run the provided {@link Runnable} where {@link PlaceholderService}s + * will replace {@link com.discordsrv.api.component.MinecraftComponent}s + * as plain without formatting (instead of converting to Discord formatting). + * @param runnable a task that will be executed immediately + */ + public static void runInPlainComponentContext(Runnable runnable) { + getInPlainComponentContext(() -> { + runnable.run(); + return null; + }); + } + + /** + * Utility method to run the provided {@link Runnable} where {@link PlaceholderService}s + * will replace {@link com.discordsrv.api.component.MinecraftComponent}s + * as plain without formatting (instead of converting to Discord formatting). + * @param supplier a supplier that will be executed immediately + * @return the output of the supplier provided as parameter + */ + public static T getInPlainComponentContext(Supplier supplier) { + PLAIN_COMPONENTS.set(true); + T output = supplier.get(); + PLAIN_COMPONENTS.set(false); + return output; + } +} diff --git a/api/src/test/java/com/discordsrv/api/placeholder/util/PlaceholdersTest.java b/api/src/test/java/com/discordsrv/api/placeholder/util/PlaceholdersTest.java index 3511c6a9..edc47281 100644 --- a/api/src/test/java/com/discordsrv/api/placeholder/util/PlaceholdersTest.java +++ b/api/src/test/java/com/discordsrv/api/placeholder/util/PlaceholdersTest.java @@ -1,3 +1,26 @@ +/* + * This file is part of the DiscordSRV API, licensed under the MIT License + * Copyright (c) 2016-2021 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package com.discordsrv.api.placeholder.util; import org.junit.jupiter.api.Test; diff --git a/build.gradle b/build.gradle index b13eb9aa..f62fb07e 100644 --- a/build.gradle +++ b/build.gradle @@ -46,12 +46,8 @@ allprojects { runtimeDownloadOnly.extendsFrom runtimeDownloadApi } - ext { - dependenciesDirectory = new File(sourceSets.main.output.resourcesDir, 'dependencies') - } - generateRuntimeDownloadResourceForRuntimeDownloadOnly { - fileLocation = new File(dependenciesDirectory, 'runtimeDownload-' + project.name + '.txt') + file = 'runtimeDownload-' + project.name + '.txt' } repositories { diff --git a/common/build.gradle b/common/build.gradle index 21b0eda4..a77fdfbc 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -3,23 +3,25 @@ import dev.vankka.dependencydownload.task.GenerateDependencyDownloadResourceTask configurations { h2Driver mysqlDriver + testRuntimeOnly.extendsFrom runtimeDownloadOnly } task generateResourceForH2Driver(type: GenerateDependencyDownloadResourceTask) { var conf = configurations.h2Driver configuration = conf - fileLocation = new File(dependenciesDirectory, conf.name + '.txt') + file = conf.name + '.txt' } task generateResourceForMySQLDriver(type: GenerateDependencyDownloadResourceTask) { var conf = configurations.mysqlDriver configuration = conf - fileLocation = new File(dependenciesDirectory, conf.name + '.txt') + file = conf.name + '.txt' } dependencies { // API annotationProcessor project(':api') compileOnlyApi project(':api') + testImplementation project(':api') // DependencyDownload api 'dev.vankka.dependencydownload:runtime:' + rootProject.ddVersion @@ -55,7 +57,7 @@ dependencies { compileOnlyApi 'org.slf4j:slf4j-api:1.7.32' } -jar { +processResources { dependsOn( generateRuntimeDownloadResourceForRuntimeDownloadOnly, generateResourceForH2Driver, diff --git a/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java b/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java index 4ef2b689..bb2c72fc 100644 --- a/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java +++ b/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java @@ -246,7 +246,7 @@ public abstract class AbstractDiscordSRV newContext = new HashSet<>(); boolean anyConverted = false; - for (Object o : event.getContext()) { + for (Object o : event.getContexts()) { Object converted; boolean isConversion = true; if (o instanceof PrivateChannel) { diff --git a/common/src/main/java/com/discordsrv/common/listener/DiscordChatListener.java b/common/src/main/java/com/discordsrv/common/listener/DiscordChatListener.java index 155650ab..0b464410 100644 --- a/common/src/main/java/com/discordsrv/common/listener/DiscordChatListener.java +++ b/common/src/main/java/com/discordsrv/common/listener/DiscordChatListener.java @@ -70,6 +70,7 @@ public class DiscordChatListener extends AbstractListener { ReceivedDiscordMessage discordMessage = event.getDiscordMessage(); DiscordUser author = discordMessage.getAuthor(); Optional member = discordMessage.getMember(); + boolean webhookMessage = discordMessage.isWebhookMessage(); OrDefault> channelPair = discordSRV.channelConfig().orDefault(channel); GameChannel gameChannel = channelPair.get(Pair::getKey); @@ -82,7 +83,6 @@ public class DiscordChatListener extends AbstractListener { DiscordToMinecraftChatConfig.Ignores ignores = chatConfig.get(cfg -> cfg.ignores); if (ignores != null) { - boolean webhookMessage = discordMessage.isWebhookMessage(); if (ignores.webhooks && webhookMessage) { return; } else if (ignores.bots && (author.isBot() && !webhookMessage)) { @@ -105,7 +105,9 @@ public class DiscordChatListener extends AbstractListener { } } - String format = chatConfig.get(cfg -> cfg.format.replace("\\n", "\n")); + String format = chatConfig.opt(cfg -> webhookMessage ? cfg.format : cfg.webhookFormat) + .map(option -> option.replace("\\n", "\n")) + .orElse(null); if (format == null) { return; } diff --git a/common/src/main/java/com/discordsrv/common/placeholder/ComponentResultStringifier.java b/common/src/main/java/com/discordsrv/common/placeholder/ComponentResultStringifier.java index 3c49ac9d..9bb866b1 100644 --- a/common/src/main/java/com/discordsrv/common/placeholder/ComponentResultStringifier.java +++ b/common/src/main/java/com/discordsrv/common/placeholder/ComponentResultStringifier.java @@ -19,14 +19,15 @@ package com.discordsrv.common.placeholder; import com.discordsrv.api.component.MinecraftComponent; -import com.discordsrv.api.placeholder.PlaceholderResultStringifier; +import com.discordsrv.api.placeholder.mapper.PlaceholderResultMapper; +import com.discordsrv.api.placeholder.mapper.ResultMappers; import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.component.util.ComponentUtil; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.jetbrains.annotations.NotNull; -public class ComponentResultStringifier implements PlaceholderResultStringifier { +public class ComponentResultStringifier implements PlaceholderResultMapper { private final DiscordSRV discordSRV; @@ -35,13 +36,13 @@ public class ComponentResultStringifier implements PlaceholderResultStringifier } @Override - public String convertPlaceholderResult(@NotNull Object result) { + public String convertResult(@NotNull Object result) { if (result instanceof MinecraftComponent) { result = ComponentUtil.fromAPI((MinecraftComponent) result); } if (result instanceof Component) { Component component = (Component) result; - if (PLAIN_COMPONENT_CONTEXT.get()) { + if (ResultMappers.isPlainComponentContext()) { return PlainTextComponentSerializer.plainText().serialize(component); } else { return discordSRV.componentFactory().discordSerializer().serialize(component); diff --git a/common/src/main/java/com/discordsrv/common/placeholder/PlaceholderServiceImpl.java b/common/src/main/java/com/discordsrv/common/placeholder/PlaceholderServiceImpl.java index eef73896..41a3d557 100644 --- a/common/src/main/java/com/discordsrv/common/placeholder/PlaceholderServiceImpl.java +++ b/common/src/main/java/com/discordsrv/common/placeholder/PlaceholderServiceImpl.java @@ -21,7 +21,7 @@ package com.discordsrv.common.placeholder; import com.discordsrv.api.event.events.placeholder.PlaceholderLookupEvent; import com.discordsrv.api.placeholder.annotation.Placeholder; import com.discordsrv.api.placeholder.PlaceholderLookupResult; -import com.discordsrv.api.placeholder.PlaceholderResultStringifier; +import com.discordsrv.api.placeholder.mapper.PlaceholderResultMapper; import com.discordsrv.api.placeholder.PlaceholderService; import com.discordsrv.api.placeholder.annotation.PlaceholderRemainder; import com.discordsrv.common.DiscordSRV; @@ -29,6 +29,7 @@ import com.discordsrv.common.placeholder.provider.AnnotationPlaceholderProvider; import com.discordsrv.common.placeholder.provider.PlaceholderProvider; import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.LoadingCache; +import org.apache.commons.lang3.StringUtils; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.jetbrains.annotations.NotNull; @@ -47,7 +48,8 @@ public class PlaceholderServiceImpl implements PlaceholderService { private final DiscordSRV discordSRV; private final LoadingCache, Set> classProviders; - private final Set stringifiers = new CopyOnWriteArraySet<>(); + private final Set mappers = new CopyOnWriteArraySet<>(); + private final Set globalContext = new CopyOnWriteArraySet<>(); public PlaceholderServiceImpl(DiscordSRV discordSRV) { this.discordSRV = discordSRV; @@ -57,10 +59,18 @@ public class PlaceholderServiceImpl implements PlaceholderService { .build(new ClassProviderLoader()); } + public void addGlobalContext(@NotNull Object context) { + globalContext.add(context); + } + + public void removeGlobalContext(@NotNull Object context) { + globalContext.remove(context); + } + private static Set getArrayAsSet(Object[] array) { return array.length == 0 - ? Collections.emptySet() - : new HashSet<>(Arrays.asList(array)); + ? Collections.emptySet() + : new HashSet<>(Arrays.asList(array)); } @Override @@ -69,34 +79,40 @@ public class PlaceholderServiceImpl implements PlaceholderService { } @Override - public PlaceholderLookupResult lookupPlaceholder(@NotNull String placeholder, @NotNull Set context) { - for (Object o : context) { - if (o == null) { + public PlaceholderLookupResult lookupPlaceholder(@NotNull String placeholder, @NotNull Set lookupContexts) { + Set contexts = new HashSet<>(lookupContexts); + contexts.addAll(globalContext); + for (Object context : contexts) { + if (context == null) { continue; } - if (o instanceof PlaceholderProvider) { - PlaceholderLookupResult result = ((PlaceholderProvider) o).lookup(placeholder, context); + if (context instanceof PlaceholderProvider) { + PlaceholderLookupResult result = ((PlaceholderProvider) context).lookup(placeholder, contexts); if (result.getType() != PlaceholderLookupResult.Type.UNKNOWN_PLACEHOLDER) { return result; } } - Set providers = classProviders.get(o instanceof Class ? (Class) o : o.getClass()); + Set providers = classProviders + .get(context instanceof Class + ? (Class) context + : context.getClass()); if (providers == null) { continue; } for (PlaceholderProvider provider : providers) { - PlaceholderLookupResult result = provider.lookup(placeholder, context); + PlaceholderLookupResult result = provider.lookup(placeholder, contexts); if (result.getType() != PlaceholderLookupResult.Type.UNKNOWN_PLACEHOLDER) { return result; } } } - // Only go through this if a placeholder couldn't be looked up from the context - PlaceholderLookupEvent lookupEvent = new PlaceholderLookupEvent(placeholder, context); + // Only go through this if a placeholder couldn't be looked up from lookup/global contexts + // API users are here as to not interfere with DiscordSRV's own placeholders + PlaceholderLookupEvent lookupEvent = new PlaceholderLookupEvent(placeholder, contexts); discordSRV.eventBus().publish(lookupEvent); return lookupEvent.isProcessed() @@ -110,13 +126,13 @@ public class PlaceholderServiceImpl implements PlaceholderService { } @Override - public void addResultStringifier(@NotNull PlaceholderResultStringifier resultStringifier) { - stringifiers.add(resultStringifier); + public void addResultMapper(@NotNull PlaceholderResultMapper resultMapper) { + mappers.add(resultMapper); } @Override - public void removeResultStringifier(@NotNull PlaceholderResultStringifier resultStringifier) { - stringifiers.remove(resultStringifier); + public void removeResultMapper(@NotNull PlaceholderResultMapper resultMapper) { + mappers.remove(resultMapper); } @Override @@ -129,9 +145,9 @@ public class PlaceholderServiceImpl implements PlaceholderService { String output = input; while (matcher.find()) { - String placeholder = matcher.group(2); - PlaceholderLookupResult result = resolve(placeholder, context); - output = updateContent(result, placeholder, matcher, output); + String placeholder = getPlaceholder(matcher); + List results = resolve(placeholder, context); + output = updateContent(results, placeholder, matcher, output); } return output; } @@ -141,9 +157,21 @@ public class PlaceholderServiceImpl implements PlaceholderService { if (matcher.groupCount() < 3) { throw new IllegalStateException("Matcher must have at least 3 groups"); } + + String placeholder = getPlaceholder(matcher); + List results = resolve(placeholder, context); + return getResultRepresentation(results, placeholder, matcher); + } + + private String getPlaceholder(Matcher matcher) { String placeholder = matcher.group(2); - PlaceholderLookupResult result = resolve(matcher, context); - return getResultRepresentation(result, placeholder, matcher); + Pattern pattern = matcher.pattern(); + if (PATTERN.equals(pattern)) { // Remove escapes for % + placeholder = placeholder.replace("\\%", "%"); + } else if (RECURSIVE_PATTERN.equals(pattern)) { // Remove escapes for { and } + placeholder = placeholder.replaceAll("\\\\([{}])", "$1"); + } + return placeholder; } @Override @@ -155,45 +183,37 @@ public class PlaceholderServiceImpl implements PlaceholderService { private String getResultAsString(Object result) { if (result == null) { return "null"; - } else if (result instanceof String) { - return (String) result; + } else if (result instanceof CharSequence) { + return result.toString(); } - String output = null; - for (PlaceholderResultStringifier stringifier : stringifiers) { - output = stringifier.convertPlaceholderResult(result); + Object output = null; + for (PlaceholderResultMapper stringifier : mappers) { + output = stringifier.convertResult(result); if (output != null) { break; } } - if (output == null) { - output = String.valueOf(result); - } - return output; + + return String.valueOf(output != null ? output : result); } - public PlaceholderLookupResult resolve(Matcher matcher, Set context) { - return resolve(matcher.group(2), context); - } - - private PlaceholderLookupResult resolve(String placeholder, Set context) { + private List resolve(String placeholder, Set context) { // Recursive placeholder = getReplacement(RECURSIVE_PATTERN, placeholder, context); - return lookupPlaceholder(placeholder, context); + List results = new ArrayList<>(); + for (String part : placeholder.split("(? results, String placeholder, Matcher matcher, String input) { + Object representation = getResultRepresentation(results, placeholder, matcher); String output = getResultAsString(representation); - for (PlaceholderResultStringifier stringifier : stringifiers) { - output = stringifier.convertPlaceholderResult(representation); - if (output != null) { - break; - } - } if (output == null) { output = String.valueOf(representation); } @@ -206,38 +226,47 @@ public class PlaceholderServiceImpl implements PlaceholderService { .replaceFirst(output); } - private Object getResultRepresentation(PlaceholderLookupResult result, String placeholder, Matcher matcher) { - while (result != null) { - PlaceholderLookupResult.Type type = result.getType(); - if (type == PlaceholderLookupResult.Type.UNKNOWN_PLACEHOLDER) { - break; - } + private Object getResultRepresentation(List results, String placeholder, Matcher matcher) { + Object best = null; + for (PlaceholderLookupResult result : results) { + while (result != null) { + PlaceholderLookupResult.Type type = result.getType(); + if (type == PlaceholderLookupResult.Type.UNKNOWN_PLACEHOLDER) { + break; + } - boolean newLookup = false; - Object replacement = null; - switch (type) { - case SUCCESS: - replacement = result.getValue(); + boolean newLookup = false; + Object replacement = null; + switch (type) { + case SUCCESS: + replacement = result.getValue(); + if (replacement != null && StringUtils.isNotBlank(getResultAsString(replacement))) { + return replacement; + } + break; + case DATA_NOT_AVAILABLE: + replacement = "Unavailable"; + break; + case LOOKUP_FAILED: + replacement = "Error"; + break; + case NEW_LOOKUP: + result = lookupPlaceholder((String) result.getValue(), result.getExtras()); + newLookup = true; + break; + } + if (replacement != null) { + best = replacement; + } + if (!newLookup) { break; - case DATA_NOT_AVAILABLE: - replacement = "Unavailable"; - break; - case LOOKUP_FAILED: - replacement = "Error"; - break; - case NEW_LOOKUP: - result = lookupPlaceholder((String) result.getValue(), result.getExtras()); - newLookup = true; - break; - } - if (replacement != null) { - return replacement; - } - if (!newLookup) { - break; + } } } - return matcher.group(1) + placeholder + matcher.group(3); + + return best != null + ? best + : matcher.group(1) + placeholder + matcher.group(3); } private static class ClassProviderLoader implements CacheLoader, Set> { diff --git a/common/src/main/java/com/discordsrv/common/placeholder/provider/AnnotationPlaceholderProvider.java b/common/src/main/java/com/discordsrv/common/placeholder/provider/AnnotationPlaceholderProvider.java index 23a01795..7598aee3 100644 --- a/common/src/main/java/com/discordsrv/common/placeholder/provider/AnnotationPlaceholderProvider.java +++ b/common/src/main/java/com/discordsrv/common/placeholder/provider/AnnotationPlaceholderProvider.java @@ -92,7 +92,7 @@ public class AnnotationPlaceholderProvider implements PlaceholderProvider { String reLookup = annotation.relookup(); if (!reLookup.isEmpty()) { if (result == null) { - return PlaceholderLookupResult.success(""); + return PlaceholderLookupResult.success(null); } Set newContext = new HashSet<>(context); @@ -101,6 +101,8 @@ public class AnnotationPlaceholderProvider implements PlaceholderProvider { return PlaceholderLookupResult.newLookup(newPlaceholder, newContext); } - return PlaceholderLookupResult.success(result); + return result instanceof PlaceholderLookupResult + ? (PlaceholderLookupResult) result + : PlaceholderLookupResult.success(result); } } diff --git a/common/src/test/java/com/discordsrv/common/MockDiscordSRV.java b/common/src/test/java/com/discordsrv/common/MockDiscordSRV.java index 0bee0f27..9a3efccd 100644 --- a/common/src/test/java/com/discordsrv/common/MockDiscordSRV.java +++ b/common/src/test/java/com/discordsrv/common/MockDiscordSRV.java @@ -1,3 +1,21 @@ +/* + * This file is part of DiscordSRV, licensed under the GPLv3 License + * Copyright (c) 2016-2021 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.discordsrv.common; import com.discordsrv.common.config.connection.ConnectionConfig; diff --git a/common/src/test/java/com/discordsrv/common/event/bus/EventBusTest.java b/common/src/test/java/com/discordsrv/common/event/bus/EventBusTest.java index 39c6c171..70f94d7c 100644 --- a/common/src/test/java/com/discordsrv/common/event/bus/EventBusTest.java +++ b/common/src/test/java/com/discordsrv/common/event/bus/EventBusTest.java @@ -1,3 +1,21 @@ +/* + * This file is part of DiscordSRV, licensed under the GPLv3 License + * Copyright (c) 2016-2021 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.discordsrv.common.event.bus; import com.discordsrv.api.event.bus.EventBus; diff --git a/common/src/test/java/com/discordsrv/common/placeholder/PlaceholderServiceTest.java b/common/src/test/java/com/discordsrv/common/placeholder/PlaceholderServiceTest.java index 43815792..c44adab0 100644 --- a/common/src/test/java/com/discordsrv/common/placeholder/PlaceholderServiceTest.java +++ b/common/src/test/java/com/discordsrv/common/placeholder/PlaceholderServiceTest.java @@ -1,3 +1,21 @@ +/* + * This file is part of DiscordSRV, licensed under the GPLv3 License + * Copyright (c) 2016-2021 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.discordsrv.common.placeholder; import com.discordsrv.api.placeholder.PlaceholderService; @@ -9,7 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class PlaceholderServiceTest { - private PlaceholderService service = MockDiscordSRV.INSTANCE.placeholderService(); + private final PlaceholderService service = MockDiscordSRV.INSTANCE.placeholderService(); @Test public void staticFieldTest() { @@ -36,6 +54,21 @@ public class PlaceholderServiceTest { assertEquals("e", service.replacePlaceholders("%static_method_with_context%", PlaceholderContext.class, "e")); } + @Test + public void orPrimaryTest() { + assertEquals("a", service.replacePlaceholders("%static_field|static_method%", PlaceholderContext.class)); + } + + @Test + public void orSecondaryTest() { + assertEquals("b", service.replacePlaceholders("%invalid|static_method%", PlaceholderContext.class)); + } + + @Test + public void orEmptyTest() { + assertEquals("b", service.replacePlaceholders("%empty|static_method%", PlaceholderContext.class)); + } + public static class PlaceholderContext { @Placeholder("static_field") @@ -46,6 +79,9 @@ public class PlaceholderServiceTest { return "b"; } + @Placeholder("empty") + public static String EMPTY = ""; + @Placeholder("object_field") public String localField = "c";