From 28fbc6c24cb563a15b3e03fd24e2ff88d7711ec7 Mon Sep 17 00:00:00 2001 From: Patrick Miller Date: Mon, 1 Jul 2024 18:43:30 -0400 Subject: [PATCH] More Keyword Tweaks (#6694) * Replace GroupKeyword with ChoiceKeyword ChoiceKeywords add support for more advanced choice groups * Fix ending keywords * Fix depth --------- Co-authored-by: Moderocky Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com> --- .../java/ch/njol/skript/patterns/Keyword.java | 142 +++++++++++------- 1 file changed, 90 insertions(+), 52 deletions(-) diff --git a/src/main/java/ch/njol/skript/patterns/Keyword.java b/src/main/java/ch/njol/skript/patterns/Keyword.java index 91dca7d86b9..c4159af547b 100644 --- a/src/main/java/ch/njol/skript/patterns/Keyword.java +++ b/src/main/java/ch/njol/skript/patterns/Keyword.java @@ -18,10 +18,17 @@ */ package ch.njol.skript.patterns; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableSet; +import org.jetbrains.annotations.Contract; + import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; /** @@ -42,57 +49,49 @@ abstract class Keyword { * @param first The pattern to build keywords from. * @return A list of all keywords within first. */ + @Contract("_ -> new") public static Keyword[] buildKeywords(PatternElement first) { + return buildKeywords(first, true, 0); + } + + /** + * Builds a list of keywords starting from the provided pattern element. + * @param first The pattern to build keywords from. + * @param starting Whether this is the start of a pattern. + * @return A list of all keywords within first. + */ + @Contract("_, _, _ -> new") + private static Keyword[] buildKeywords(PatternElement first, boolean starting, int depth) { List keywords = new ArrayList<>(); PatternElement next = first; - boolean starting = true; // whether it is the start of the pattern - boolean ending = next.next == null; // whether it is the end of the pattern while (next != null) { if (next instanceof LiteralPatternElement) { // simple literal strings are keywords String literal = next.toString().trim(); while (literal.contains(" ")) literal = literal.replace(" ", " "); - keywords.add(new SimpleKeyword(literal, starting, ending)); - } else if (next instanceof ChoicePatternElement) { // this element might contain some keywords - List choiceElements = flatten(next); - if (choiceElements.stream().allMatch(e -> e instanceof LiteralPatternElement)) { - // all elements are literals, and this is a choice, meaning one of them must be required - // thus, we build a keyword that requires one of them to be present. - List groupKeywords = choiceElements.stream() - .map(e -> { - String literal = e.toString().trim(); - while (literal.contains(" ")) - literal = literal.replace(" ", " "); - return literal; - }) - .collect(Collectors.toList()); - keywords.add(new GroupKeyword(groupKeywords, starting, ending)); - } - } else if (next instanceof GroupPatternElement) { // groups need to be unwrapped (they might contain choices) - next = ((GroupPatternElement) next).getPatternElement(); - continue; + if (!literal.isEmpty()) // empty string is not useful + keywords.add(new SimpleKeyword(literal, starting, next.next == null)); + } else if (depth <= 1 && next instanceof ChoicePatternElement) { // attempt to build keywords from choices + final boolean finalStarting = starting; + final int finalDepth = depth; + // build the keywords for each choice + Set> choices = ((ChoicePatternElement) next).getPatternElements().stream() + .map(element -> buildKeywords(element, finalStarting, finalDepth)) + .map(ImmutableSet::copyOf) + .collect(Collectors.toSet()); + if (choices.stream().noneMatch(Collection::isEmpty)) // each choice must have a keyword for this to work + keywords.add(new ChoiceKeyword(choices)); // a keyword where only one choice much + } else if (next instanceof GroupPatternElement) { // add in keywords from the group + Collections.addAll(keywords, buildKeywords(((GroupPatternElement) next).getPatternElement(), starting, depth + 1)); } - starting = false; - next = next.next; - } - return keywords.toArray(new Keyword[0]); - } - /** - * A method for flattening a pattern element. - * For example, a {@link ChoicePatternElement} wraps multiple elements. This method unwraps it. - * @param element The element to flatten. - * @return A list of all pattern elements contained within element. - */ - private static List flatten(PatternElement element) { - if (element instanceof ChoicePatternElement) { - return ((ChoicePatternElement) element).getPatternElements().stream() - .flatMap(e -> flatten(e).stream()) - .collect(Collectors.toList()); - } else if (element instanceof GroupPatternElement) { - element = ((GroupPatternElement) element).getPatternElement(); + // a parse tag does not represent actual content in a pattern, therefore it should not affect starting + if (!(next instanceof ParseTagPatternElement)) + starting = false; + + next = next.originalNext; } - return Collections.singletonList(element); + return keywords.toArray(new Keyword[0]); } /** @@ -118,31 +117,70 @@ public boolean isPresent(String expr) { return expr.contains(keyword); } + @Override + public int hashCode() { + return Objects.hash(keyword, starting, ending); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof SimpleKeyword)) + return false; + SimpleKeyword other = (SimpleKeyword) obj; + return this.keyword.equals(other.keyword) && + this.starting == other.starting && + this.ending == other.ending; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("keyword", keyword) + .add("starting", starting) + .add("ending", ending) + .toString(); + } + } /** * A keyword implementation that requires at least one string out of a collection of strings to be present. */ - private static final class GroupKeyword extends Keyword { + private static final class ChoiceKeyword extends Keyword { - private final Collection keywords; - private final boolean starting, ending; + private final Set> choices; - GroupKeyword(Collection keywords, boolean starting, boolean ending) { - this.keywords = keywords; - this.starting = starting; - this.ending = ending; + ChoiceKeyword(Set> choices) { + this.choices = choices; } @Override public boolean isPresent(String expr) { - if (starting) - return keywords.stream().anyMatch(expr::startsWith); - if (ending) - return keywords.stream().anyMatch(expr::endsWith); - return keywords.stream().anyMatch(expr::contains); + return choices.stream().anyMatch(keywords -> keywords.stream().allMatch(keyword -> keyword.isPresent(expr))); } + @Override + public int hashCode() { + return Arrays.hashCode(choices.toArray()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof ChoiceKeyword)) + return false; + return choices.equals(((ChoiceKeyword) obj).choices); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("choices", choices.stream().map(Object::toString).collect(Collectors.joining(", "))) + .toString(); + } } }