From 0a4cdd01ce51afe20cf0e9f2a00014805ae80ea5 Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Sat, 8 Feb 2020 01:44:19 +0100 Subject: [PATCH] New approach for chat processing Created different providers for regex, range and string matching Handling one match at a time per provider TODO: Formatters should be executed one by one in declaration order Add enitre message by default Loop until all of the providers are finished Ignore any areas returned by the providers #71 --- .../components/formatter/ChatProcessing.java | 6 +- .../formatter/formatting/ChatFormatUtils.java | 31 ++++++ .../formatter/formatting/ChatFormatter.java | 104 ++++-------------- .../formatter/formatting/FormatSettings.java | 18 +++ .../formatting/FormattedSection.java | 25 +---- .../formatter/formatting/MatchProvider.java | 11 ++ .../formatting/RangeMatchProvider.java | 39 +++++++ .../formatting/RegexMatchProvider.java | 44 ++++++++ .../formatting/StringMatchProvider.java | 42 +++++++ 9 files changed, 214 insertions(+), 106 deletions(-) create mode 100644 src/main/java/buttondevteam/chat/components/formatter/formatting/ChatFormatUtils.java create mode 100644 src/main/java/buttondevteam/chat/components/formatter/formatting/FormatSettings.java create mode 100644 src/main/java/buttondevteam/chat/components/formatter/formatting/MatchProvider.java create mode 100644 src/main/java/buttondevteam/chat/components/formatter/formatting/RangeMatchProvider.java create mode 100644 src/main/java/buttondevteam/chat/components/formatter/formatting/RegexMatchProvider.java create mode 100644 src/main/java/buttondevteam/chat/components/formatter/formatting/StringMatchProvider.java diff --git a/src/main/java/buttondevteam/chat/components/formatter/ChatProcessing.java b/src/main/java/buttondevteam/chat/components/formatter/ChatProcessing.java index f83933d..e099775 100644 --- a/src/main/java/buttondevteam/chat/components/formatter/ChatProcessing.java +++ b/src/main/java/buttondevteam/chat/components/formatter/ChatProcessing.java @@ -49,7 +49,6 @@ public class ChatProcessing { private static final Pattern CONSOLE_PING_PATTERN = Pattern.compile("(?i)" + Pattern.quote("@console")); private static final Pattern HASHTAG_PATTERN = Pattern.compile("#(\\w+)"); private static final Pattern URL_PATTERN = Pattern.compile("(http[\\w:/?=$\\-_.+!*'(),&]+(?:#[\\w]+)?)"); - public static final Pattern ENTIRE_MESSAGE_PATTERN = Pattern.compile(".+"); private static final Pattern UNDERLINED_PATTERN = Pattern.compile("__"); private static final Pattern ITALIC_PATTERN = Pattern.compile("\\*"); private static final Pattern ITALIC_PATTERN_2 = Pattern.compile("_"); @@ -64,7 +63,10 @@ public class ChatProcessing { private static final Pattern WORD_PATTERN = Pattern.compile("\\S+"); private static boolean pingedconsole = false; - public static final ChatFormatter ESCAPE_FORMATTER = ChatFormatter.builder("escape", ESCAPE_PATTERN).build(); + /** + * A special range formatter that removes the effect of the next formatter + */ + public static final ChatFormatter ESCAPE_FORMATTER = ChatFormatter.builder("escape", ESCAPE_PATTERN).type(ChatFormatter.Type.Range).build(); private static ArrayList commonFormatters = Lists.newArrayList( ChatFormatter.builder("bold", BOLD_PATTERN).bold(true).removeCharCount((short) 2).type(ChatFormatter.Type.Range) diff --git a/src/main/java/buttondevteam/chat/components/formatter/formatting/ChatFormatUtils.java b/src/main/java/buttondevteam/chat/components/formatter/formatting/ChatFormatUtils.java new file mode 100644 index 0000000..00f6018 --- /dev/null +++ b/src/main/java/buttondevteam/chat/components/formatter/formatting/ChatFormatUtils.java @@ -0,0 +1,31 @@ +package buttondevteam.chat.components.formatter.formatting; + +import buttondevteam.chat.commands.ucmds.admin.DebugCommand; + +import java.util.ArrayList; +import java.util.Arrays; + +public final class ChatFormatUtils { + private ChatFormatUtils() {} + + static void sendMessageWithPointer(String str, int... pointer) { + DebugCommand.SendDebugMessage(str); + StringBuilder sb = new StringBuilder(str.length()); + Arrays.sort(pointer); + for (int i = 0; i < pointer.length; i++) { + for (int j = 0; j < pointer[i] - (i > 0 ? pointer[i - 1] + 1 : 0); j++) + sb.append(' '); + if (pointer[i] == (i > 0 ? pointer[i - 1] : -1)) + continue; + sb.append('^'); + } + DebugCommand.SendDebugMessage(sb.toString()); + } + + /** + * Check if the given start and end position is inside any of the ranges + */ + static boolean isInRange(int start, int end, ArrayList ranges) { + return ranges.stream().anyMatch(range -> range[1] >= start && range[0] <= end - 1); + } +} diff --git a/src/main/java/buttondevteam/chat/components/formatter/formatting/ChatFormatter.java b/src/main/java/buttondevteam/chat/components/formatter/formatting/ChatFormatter.java index 14294f3..b9f6e4a 100644 --- a/src/main/java/buttondevteam/chat/components/formatter/formatting/ChatFormatter.java +++ b/src/main/java/buttondevteam/chat/components/formatter/formatting/ChatFormatter.java @@ -5,14 +5,12 @@ import buttondevteam.chat.components.formatter.ChatProcessing; import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.architecture.IHaveConfig; import buttondevteam.lib.chat.Color; -import buttondevteam.lib.chat.Priority; import lombok.Builder; import lombok.Data; import lombok.val; import java.util.*; import java.util.function.Predicate; -import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -28,16 +26,6 @@ import java.util.stream.Collectors; @Builder public final class ChatFormatter { Pattern regex; - boolean italic; - boolean bold; - boolean underlined; - boolean strikethrough; - boolean obfuscated; - Color color; - TriFunc onmatch; - String openlink; - @Builder.Default - Priority priority = Priority.Normal; @Builder.Default short removeCharCount = 0; @Builder.Default @@ -64,18 +52,6 @@ public final class ChatFormatter { return config.getData(name + ".enabled", true); } - public enum Type { - Normal, - /** - * Matches a start and an end section which gets converted to one section (for example see italics) - */ - Range, - /** - * Exclude matching area from further processing (besides this formatter) - */ - Excluder - } - @FunctionalInterface public interface TriFunc { R apply(T1 x1, T2 x2, T3 x3); @@ -101,8 +77,8 @@ public final class ChatFormatter { */ val remchars = new ArrayList(); - header("Range section conversion"); - sections = convertRangeSections(str, sections, remchars); + header("Range section creation"); + sections = createRangeSections(str, sections, formatters, remchars); header("Adding remove chars (RC)"); // Important to add after the range section conversion addRemChars(sections, remchars, str); @@ -117,28 +93,6 @@ public final class ChatFormatter { private static void createSections(List formatters, String str, ArrayList sections, boolean excluders) { - for (ChatFormatter formatter : formatters) { - if (excluders == (formatter.type != Type.Excluder)) - continue; //If we're looking at excluders and this isn't one, skip - or vica-versa - Matcher matcher = formatter.regex.matcher(str); - while (matcher.find()) { - DebugCommand.SendDebugMessage("Found match from " + matcher.start() + " to " + (matcher.end() - 1)); - DebugCommand.SendDebugMessage("With " + (excluders ? "excluder " : "") + "formatter: " + formatter); - sendMessageWithPointer(str, matcher.start(), matcher.end() - 1); - if (formatter.regex != ChatProcessing.ENTIRE_MESSAGE_PATTERN && sections.stream().anyMatch(fs -> fs.type == Type.Excluder && (fs.End >= matcher.start() && fs.Start <= matcher.end() - 1))) { - DebugCommand.SendDebugMessage("Ignoring formatter because of an excluder"); - continue; //Exclude areas matched by excluders - Range sections are correctly handled afterwards - } - ArrayList groups = new ArrayList<>(); - for (int i = 0; i < matcher.groupCount(); i++) - groups.add(matcher.group(i + 1)); - if (groups.size() > 0) - DebugCommand.SendDebugMessage("First group: " + groups.get(0)); - FormattedSection section = new FormattedSection(formatter, matcher.start(), matcher.end() - 1, groups, - formatter.type); - sections.add(section); - } - } } private static void newCombine(String str, ArrayList sections, ArrayList remchars) { @@ -154,7 +108,7 @@ public final class ChatFormatter { } } - private static ArrayList convertRangeSections(String str, ArrayList sections, ArrayList remchars) { + private static ArrayList createRangeSections(String str, List formatters, ArrayList remchars) { ArrayList combined = new ArrayList<>(); Map nextSection = new HashMap<>(); boolean escaped = false; @@ -172,7 +126,7 @@ public final class ChatFormatter { combined.add(section); // The above will delete the \ DebugCommand.SendDebugMessage("Added section: " + section); } - sendMessageWithPointer(str, section.Start, section.End); + ChatFormatUtils.sendMessageWithPointer(str, section.Start, section.End); continue; } if (!escaped) { @@ -183,17 +137,17 @@ public final class ChatFormatter { */ if (takenByBigGuy || formatter.removeCharCount < takenEnd - takenStart) { DebugCommand.SendDebugMessage("Lose: " + section); - sendMessageWithPointer(str, section.Start, section.End); + ChatFormatUtils.sendMessageWithPointer(str, section.Start, section.End); DebugCommand.SendDebugMessage("And win: " + takenFormatter); continue; // The current section loses } nextSection.remove(takenFormatter); // The current section wins DebugCommand.SendDebugMessage("Win: " + section); - sendMessageWithPointer(str, section.Start, section.End); + ChatFormatUtils.sendMessageWithPointer(str, section.Start, section.End); DebugCommand.SendDebugMessage("And lose: " + takenFormatter); } boolean hasFormatter = nextSection.containsKey(formatter); - if (!hasFormatter) { + /*if (!hasFormatter) { val ff = formatter; val cfo = nextSection.keySet().stream().filter(f -> f.removeCharCount > ff.removeCharCount).findAny(); if (cfo.isPresent()) { @@ -205,7 +159,7 @@ public final class ChatFormatter { continue; //Not the formatter we're looking for - TODO: It doesn't fix the problem of italics at the end } } - } + }*/ takenStart = section.Start; takenEnd = section.Start + formatter.removeCharCount; takenFormatter = formatter; @@ -218,19 +172,19 @@ public final class ChatFormatter { combined.add(s); takenByBigGuy = true; DebugCommand.SendDebugMessage("Finished section: " + s); - sendMessageWithPointer(str, s.Start, s.End); + ChatFormatUtils.sendMessageWithPointer(str, s.Start, s.End); } else { DebugCommand.SendDebugMessage("Adding next section: " + section); - sendMessageWithPointer(str, section.Start, section.End); + ChatFormatUtils.sendMessageWithPointer(str, section.Start, section.End); nextSection.put(formatter, section); takenByBigGuy = false; } DebugCommand .SendDebugMessage("New area taken: (" + takenStart + "-" + takenEnd + ") " + takenFormatter); - sendMessageWithPointer(str, takenStart, takenEnd); + ChatFormatUtils.sendMessageWithPointer(str, takenStart, takenEnd); } else { DebugCommand.SendDebugMessage("Skipping section: " + section); // This will keep the text (character) - sendMessageWithPointer(str, section.Start, section.End); + ChatFormatUtils.sendMessageWithPointer(str, section.Start, section.End); escaped = false; // Reset escaping if applied, like if we're at the '*' in '\*' } } @@ -250,7 +204,7 @@ public final class ChatFormatter { .forEach(remchars::add); DebugCommand.SendDebugMessage("Added remchars:"); DebugCommand.SendDebugMessage(remchars.stream().map(Arrays::toString).collect(Collectors.joining("; "))); - sendMessageWithPointer(str, + ChatFormatUtils.sendMessageWithPointer(str, remchars.stream().flatMapToInt(Arrays::stream).toArray()); } @@ -271,15 +225,15 @@ public final class ChatFormatter { lastSection = lastSect; } DebugCommand.SendDebugMessage("Combining sections " + firstSection); - sendMessageWithPointer(str, firstSection.Start, firstSection.End); + ChatFormatUtils.sendMessageWithPointer(str, firstSection.Start, firstSection.End); DebugCommand.SendDebugMessage(" and " + lastSection); - sendMessageWithPointer(str, lastSection.Start, lastSection.End); + ChatFormatUtils.sendMessageWithPointer(str, lastSection.Start, lastSection.End); if (firstSection.Start == lastSection.Start && firstSection.End == lastSection.End) { firstSection.Formatters.addAll(lastSection.Formatters); firstSection.Matches.addAll(lastSection.Matches); firstSection.type = lastSection.type; DebugCommand.SendDebugMessage("To section " + firstSection); - sendMessageWithPointer(str, firstSection.Start, firstSection.End); + ChatFormatUtils.sendMessageWithPointer(str, firstSection.Start, firstSection.End); sections.remove(i); i = 0; sortSections(sections); @@ -305,7 +259,7 @@ public final class ChatFormatter { Predicate removeIfNeeded = s -> { if (s.Start < 0 || s.End < 0 || s.Start > s.End) { DebugCommand.SendDebugMessage(" Removed: " + s); - sendMessageWithPointer(str, s.Start, s.End); + ChatFormatUtils.sendMessageWithPointer(str, s.Start, s.End); sections.remove(s); return true; } @@ -315,15 +269,15 @@ public final class ChatFormatter { DebugCommand.SendDebugMessage("To sections"); if (!removeIfNeeded.test(firstSection)) { DebugCommand.SendDebugMessage(" 1:" + firstSection + ""); - sendMessageWithPointer(str, firstSection.Start, firstSection.End); + ChatFormatUtils.sendMessageWithPointer(str, firstSection.Start, firstSection.End); } if (!removeIfNeeded.test(section)) { DebugCommand.SendDebugMessage(" 2:" + section + ""); - sendMessageWithPointer(str, section.Start, section.End); + ChatFormatUtils.sendMessageWithPointer(str, section.Start, section.End); } if (!removeIfNeeded.test(lastSection)) { DebugCommand.SendDebugMessage(" 3:" + lastSection); - sendMessageWithPointer(str, lastSection.Start, lastSection.End); + ChatFormatUtils.sendMessageWithPointer(str, lastSection.Start, lastSection.End); } i = 0; } @@ -332,7 +286,7 @@ public final class ChatFormatter { for (int j = i - 1; j <= i + 1; j++) { if (j < sections.size() && sections.get(j).End < sections.get(j).Start) { DebugCommand.SendDebugMessage("Removing section: " + sections.get(j)); - sendMessageWithPointer(str, sections.get(j).Start, sections.get(j).End); + ChatFormatUtils.sendMessageWithPointer(str, sections.get(j).Start, sections.get(j).End); sections.remove(j); j--; i = 0; @@ -349,7 +303,7 @@ public final class ChatFormatter { String originaltext; int start = section.Start, end = section.End; DebugCommand.SendDebugMessage("Start: " + start + " - End: " + end); - sendMessageWithPointer(str, start, end); + ChatFormatUtils.sendMessageWithPointer(str, start, end); /*DebugCommand.SendDebugMessage("RCS: "+remchars.stream().filter(rc -> rc[0] <= start && start <= rc[1]).count()); DebugCommand.SendDebugMessage("RCE: "+remchars.stream().filter(rc -> rc[0] <= end && end <= rc[1]).count()); DebugCommand.SendDebugMessage("RCI: "+remchars.stream().filter(rc -> start < rc[0] || rc[1] < end).count());*/ @@ -432,20 +386,6 @@ public final class ChatFormatter { : Integer.compare(s1.Start, s2.Start)); } - private static void sendMessageWithPointer(String str, int... pointer) { - DebugCommand.SendDebugMessage(str); - StringBuilder sb = new StringBuilder(str.length()); - Arrays.sort(pointer); - for (int i = 0; i < pointer.length; i++) { - for (int j = 0; j < pointer[i] - (i > 0 ? pointer[i - 1] + 1 : 0); j++) - sb.append(' '); - if (pointer[i] == (i > 0 ? pointer[i - 1] : -1)) - continue; - sb.append('^'); - } - DebugCommand.SendDebugMessage(sb.toString()); - } - private static void header(String message) { DebugCommand.SendDebugMessage("\n--------\n" + message + "\n--------\n"); } diff --git a/src/main/java/buttondevteam/chat/components/formatter/formatting/FormatSettings.java b/src/main/java/buttondevteam/chat/components/formatter/formatting/FormatSettings.java new file mode 100644 index 0000000..39dce46 --- /dev/null +++ b/src/main/java/buttondevteam/chat/components/formatter/formatting/FormatSettings.java @@ -0,0 +1,18 @@ +package buttondevteam.chat.components.formatter.formatting; + +import buttondevteam.lib.chat.Color; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class FormatSettings { + boolean italic; + boolean bold; + boolean underlined; + boolean strikethrough; + boolean obfuscated; + Color color; + ChatFormatter.TriFunc onmatch; + String openlink; +} diff --git a/src/main/java/buttondevteam/chat/components/formatter/formatting/FormattedSection.java b/src/main/java/buttondevteam/chat/components/formatter/formatting/FormattedSection.java index c609e58..0345479 100644 --- a/src/main/java/buttondevteam/chat/components/formatter/formatting/FormattedSection.java +++ b/src/main/java/buttondevteam/chat/components/formatter/formatting/FormattedSection.java @@ -1,36 +1,17 @@ package buttondevteam.chat.components.formatter.formatting; import java.util.ArrayList; -import java.util.Collection; public class FormattedSection { public int Start; public int End; - public ArrayList Formatters = new ArrayList(); + public FormatSettings Settings; public ArrayList Matches = new ArrayList(); - public ChatFormatter.Type type; - FormattedSection(ChatFormatter formatter, int start, int end, ArrayList matches, ChatFormatter.Type type) { + FormattedSection(FormatSettings settings, int start, int end, ArrayList matches) { Start = start; End = end; - Formatters.add(formatter); + Settings = settings; Matches.addAll(matches); - this.type = type; - } - - FormattedSection(Collection formatters, int start, int end, ArrayList matches, - ChatFormatter.Type type) { - Start = start; - End = end; - Formatters.addAll(formatters); - Matches.addAll(matches); - this.type = type; - } - - @Override - public String toString() { - return "Section(" + Start + ", " + End + ", formatters: " + - Formatters.toString() + ", matches: " + Matches.toString() + ", " + - type + ")"; } } \ No newline at end of file diff --git a/src/main/java/buttondevteam/chat/components/formatter/formatting/MatchProvider.java b/src/main/java/buttondevteam/chat/components/formatter/formatting/MatchProvider.java new file mode 100644 index 0000000..db6a388 --- /dev/null +++ b/src/main/java/buttondevteam/chat/components/formatter/formatting/MatchProvider.java @@ -0,0 +1,11 @@ +package buttondevteam.chat.components.formatter.formatting; + +import javax.annotation.Nullable; +import java.util.ArrayList; + +public interface MatchProvider { + @Nullable + FormattedSection getNextSection(String message, ArrayList ignoredAreas, ArrayList removedCharacters); + + boolean isFinished(); +} diff --git a/src/main/java/buttondevteam/chat/components/formatter/formatting/RangeMatchProvider.java b/src/main/java/buttondevteam/chat/components/formatter/formatting/RangeMatchProvider.java new file mode 100644 index 0000000..eefe32b --- /dev/null +++ b/src/main/java/buttondevteam/chat/components/formatter/formatting/RangeMatchProvider.java @@ -0,0 +1,39 @@ +package buttondevteam.chat.components.formatter.formatting; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.ArrayList; + +@RequiredArgsConstructor +public class RangeMatchProvider implements MatchProvider { + private final String pattern; + private final FormatSettings settings; + @Getter + private boolean finished; + private int nextIndex = 0; + private FormattedSection startedSection; + + @SuppressWarnings("DuplicatedCode") + @Override + public FormattedSection getNextSection(String message, ArrayList ignoredAreas, ArrayList removedCharacters) { + int i, len; + do { + i = message.indexOf(pattern, nextIndex); + len = pattern.length(); + nextIndex = i + len; //Set for the next loop if it's escaped or for the next method call + } while (i > 0 && message.charAt(i - 1) == '\\'); + if (i == -1) { + finished = true; //Won't find any more - unfinished sections will be garbage collected + return null; + } + removedCharacters.add(new int[]{i, i + len - 1}); + if (startedSection == null) { + startedSection = new FormattedSection(settings, i, i + len - 1, new ArrayList<>(0)); + return null; + } else { + startedSection.End = i + len - 1; + return startedSection; + } + } +} diff --git a/src/main/java/buttondevteam/chat/components/formatter/formatting/RegexMatchProvider.java b/src/main/java/buttondevteam/chat/components/formatter/formatting/RegexMatchProvider.java new file mode 100644 index 0000000..af2fc72 --- /dev/null +++ b/src/main/java/buttondevteam/chat/components/formatter/formatting/RegexMatchProvider.java @@ -0,0 +1,44 @@ +package buttondevteam.chat.components.formatter.formatting; + +import buttondevteam.chat.commands.ucmds.admin.DebugCommand; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@RequiredArgsConstructor +public class RegexMatchProvider implements MatchProvider { + private final Pattern pattern; + private final FormatSettings settings; + private Matcher matcher; + @Getter + private boolean finished; + + @Nullable + @Override + public FormattedSection getNextSection(String message, ArrayList ignoredAreas, ArrayList removedCharacters) { + if (matcher == null) + matcher = pattern.matcher(message); + if (!matcher.find()) { + finished = true; + return null; + } + int start = matcher.start(), end = matcher.end() - 1; + DebugCommand.SendDebugMessage("Found regex match from " + start + " to " + end); + DebugCommand.SendDebugMessage("With settings: " + settings); + ChatFormatUtils.sendMessageWithPointer(message, start, end); + if (ChatFormatUtils.isInRange(start, end, ignoredAreas)) { + DebugCommand.SendDebugMessage("Formatter is in ignored area, skipping"); + return null; //Not setting finished to true, so it will go to the next match + } + ArrayList groups = new ArrayList<>(); + for (int i = 0; i < matcher.groupCount(); i++) + groups.add(matcher.group(i + 1)); + if (groups.size() > 0) + DebugCommand.SendDebugMessage("First group: " + groups.get(0)); + return new FormattedSection(settings, matcher.start(), matcher.end() - 1, groups); + } +} diff --git a/src/main/java/buttondevteam/chat/components/formatter/formatting/StringMatchProvider.java b/src/main/java/buttondevteam/chat/components/formatter/formatting/StringMatchProvider.java new file mode 100644 index 0000000..bcab617 --- /dev/null +++ b/src/main/java/buttondevteam/chat/components/formatter/formatting/StringMatchProvider.java @@ -0,0 +1,42 @@ +package buttondevteam.chat.components.formatter.formatting; + +import lombok.Getter; + +import javax.annotation.Nullable; +import java.util.ArrayList; + +public class StringMatchProvider implements MatchProvider { + private final String[] strings; + private final FormatSettings settings; + @Getter + private boolean finished; + private int nextIndex = 0; + + /** + * Matches the given strings in the order given + * + * @param settings The format settings + * @param strings The strings to match in the correct order + */ + public StringMatchProvider(FormatSettings settings, String... strings) { + this.settings = settings; + this.strings = strings; + } + + @Nullable + @Override + public FormattedSection getNextSection(String message, ArrayList ignoredAreas, ArrayList removedCharacters) { + int i = -1, len = 0; + for (String string : strings) { + i = message.indexOf(string, nextIndex); + len = string.length(); + if (i != -1) break; + } + if (i == -1) { + finished = true; //Won't find any more + return null; + } + nextIndex = i + len; + return new FormattedSection(settings, i, i + len - 1, new ArrayList<>(0)); + } +}