From 6525503f7884d0bd0b5e36c685c16e46ea5c2735 Mon Sep 17 00:00:00 2001 From: libraryaddict Date: Fri, 26 Oct 2018 02:03:00 +1300 Subject: [PATCH] Move split(string) to DisguiseUtilities and add unit testing --- .../disguise/DisguiseConfig.java | 2 +- .../disguise/commands/DisguiseCommand.java | 3 +- .../commands/DisguiseEntityCommand.java | 4 +- .../commands/DisguiseModifyCommand.java | 3 +- .../commands/DisguiseModifyEntityCommand.java | 3 +- .../commands/DisguiseModifyPlayerCommand.java | 3 +- .../commands/DisguiseModifyRadiusCommand.java | 3 +- .../commands/DisguisePlayerCommand.java | 3 +- .../commands/DisguiseRadiusCommand.java | 3 +- .../disguise/utilities/DisguiseUtilities.java | 94 ++++++++++++++ .../utilities/parser/DisguiseParser.java | 17 --- .../utilities/DisguiseUtilitiesTest.java | 120 ++++++++++++++++++ 12 files changed, 231 insertions(+), 27 deletions(-) create mode 100644 src/test/java/me/libraryaddict/disguise/utilities/DisguiseUtilitiesTest.java diff --git a/src/main/java/me/libraryaddict/disguise/DisguiseConfig.java b/src/main/java/me/libraryaddict/disguise/DisguiseConfig.java index 2429d32b..bbbe1813 100644 --- a/src/main/java/me/libraryaddict/disguise/DisguiseConfig.java +++ b/src/main/java/me/libraryaddict/disguise/DisguiseConfig.java @@ -292,7 +292,7 @@ public class DisguiseConfig { try { Disguise disguise = DisguiseParser - .parseDisguise(Bukkit.getConsoleSender(), "disguise", DisguiseParser.split(toParse), + .parseDisguise(Bukkit.getConsoleSender(), "disguise", DisguiseUtilities.split(toParse), DisguiseParser.getPermissions(Bukkit.getConsoleSender(), "disguise")); customDisguises.put(key, disguise); diff --git a/src/main/java/me/libraryaddict/disguise/commands/DisguiseCommand.java b/src/main/java/me/libraryaddict/disguise/commands/DisguiseCommand.java index 9c582c9d..48652e59 100644 --- a/src/main/java/me/libraryaddict/disguise/commands/DisguiseCommand.java +++ b/src/main/java/me/libraryaddict/disguise/commands/DisguiseCommand.java @@ -5,6 +5,7 @@ import me.libraryaddict.disguise.DisguiseConfig; import me.libraryaddict.disguise.disguisetypes.Disguise; import me.libraryaddict.disguise.disguisetypes.DisguiseType; import me.libraryaddict.disguise.disguisetypes.watchers.LivingWatcher; +import me.libraryaddict.disguise.utilities.DisguiseUtilities; import me.libraryaddict.disguise.utilities.LibsMsg; import me.libraryaddict.disguise.utilities.parser.*; import me.libraryaddict.disguise.utilities.parser.params.ParamInfo; @@ -38,7 +39,7 @@ public class DisguiseCommand extends DisguiseBaseCommand implements TabCompleter try { disguise = DisguiseParser - .parseDisguise(sender, getPermNode(), DisguiseParser.split(StringUtils.join(args, " ")), + .parseDisguise(sender, getPermNode(), DisguiseUtilities.split(StringUtils.join(args, " ")), getPermissions(sender)); } catch (DisguiseParseException ex) { diff --git a/src/main/java/me/libraryaddict/disguise/commands/DisguiseEntityCommand.java b/src/main/java/me/libraryaddict/disguise/commands/DisguiseEntityCommand.java index 69bbe536..027b5d61 100644 --- a/src/main/java/me/libraryaddict/disguise/commands/DisguiseEntityCommand.java +++ b/src/main/java/me/libraryaddict/disguise/commands/DisguiseEntityCommand.java @@ -4,6 +4,7 @@ import me.libraryaddict.disguise.DisguiseConfig; import me.libraryaddict.disguise.LibsDisguises; import me.libraryaddict.disguise.disguisetypes.Disguise; import me.libraryaddict.disguise.disguisetypes.DisguiseType; +import me.libraryaddict.disguise.utilities.DisguiseUtilities; import me.libraryaddict.disguise.utilities.LibsMsg; import me.libraryaddict.disguise.utilities.parser.*; import me.libraryaddict.disguise.utilities.parser.params.ParamInfo; @@ -18,7 +19,6 @@ import org.bukkit.entity.Player; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; public class DisguiseEntityCommand extends DisguiseBaseCommand implements TabCompleter { @@ -43,7 +43,7 @@ public class DisguiseEntityCommand extends DisguiseBaseCommand implements TabCom try { disguise = DisguiseParser - .parseDisguise(sender, getPermNode(), DisguiseParser.split(StringUtils.join(args, " ")), + .parseDisguise(sender, getPermNode(), DisguiseUtilities.split(StringUtils.join(args, " ")), getPermissions(sender)); } catch (DisguiseParseException ex) { diff --git a/src/main/java/me/libraryaddict/disguise/commands/DisguiseModifyCommand.java b/src/main/java/me/libraryaddict/disguise/commands/DisguiseModifyCommand.java index 1a911390..7d73a479 100644 --- a/src/main/java/me/libraryaddict/disguise/commands/DisguiseModifyCommand.java +++ b/src/main/java/me/libraryaddict/disguise/commands/DisguiseModifyCommand.java @@ -3,6 +3,7 @@ package me.libraryaddict.disguise.commands; import me.libraryaddict.disguise.DisguiseAPI; import me.libraryaddict.disguise.disguisetypes.Disguise; import me.libraryaddict.disguise.disguisetypes.DisguiseType; +import me.libraryaddict.disguise.utilities.DisguiseUtilities; import me.libraryaddict.disguise.utilities.LibsMsg; import me.libraryaddict.disguise.utilities.parser.*; import me.libraryaddict.disguise.utilities.parser.params.ParamInfo; @@ -55,7 +56,7 @@ public class DisguiseModifyCommand extends DisguiseBaseCommand implements TabCom try { DisguiseParser.callMethods(sender, disguise, permissions, disguisePerm, new ArrayList<>(), - DisguiseParser.split(StringUtils.join(args, " "))); + DisguiseUtilities.split(StringUtils.join(args, " "))); } catch (DisguiseParseException ex) { if (ex.getMessage() != null) { diff --git a/src/main/java/me/libraryaddict/disguise/commands/DisguiseModifyEntityCommand.java b/src/main/java/me/libraryaddict/disguise/commands/DisguiseModifyEntityCommand.java index 4ccf95f6..3fcd08e6 100644 --- a/src/main/java/me/libraryaddict/disguise/commands/DisguiseModifyEntityCommand.java +++ b/src/main/java/me/libraryaddict/disguise/commands/DisguiseModifyEntityCommand.java @@ -2,6 +2,7 @@ package me.libraryaddict.disguise.commands; import me.libraryaddict.disguise.DisguiseConfig; import me.libraryaddict.disguise.LibsDisguises; +import me.libraryaddict.disguise.utilities.DisguiseUtilities; import me.libraryaddict.disguise.utilities.LibsMsg; import me.libraryaddict.disguise.utilities.parser.DisguiseParser; import me.libraryaddict.disguise.utilities.parser.DisguisePerm; @@ -43,7 +44,7 @@ public class DisguiseModifyEntityCommand extends DisguiseBaseCommand implements // TODO Validate if any disguises have this arg LibsDisguises.getInstance().getListener() - .setDisguiseModify(sender.getName(), DisguiseParser.split(StringUtils.join(args, " "))); + .setDisguiseModify(sender.getName(), DisguiseUtilities.split(StringUtils.join(args, " "))); sender.sendMessage(LibsMsg.DMODIFYENT_CLICK.get(DisguiseConfig.getDisguiseEntityExpire())); return true; diff --git a/src/main/java/me/libraryaddict/disguise/commands/DisguiseModifyPlayerCommand.java b/src/main/java/me/libraryaddict/disguise/commands/DisguiseModifyPlayerCommand.java index b44147c8..a3078979 100644 --- a/src/main/java/me/libraryaddict/disguise/commands/DisguiseModifyPlayerCommand.java +++ b/src/main/java/me/libraryaddict/disguise/commands/DisguiseModifyPlayerCommand.java @@ -2,6 +2,7 @@ package me.libraryaddict.disguise.commands; import me.libraryaddict.disguise.DisguiseAPI; import me.libraryaddict.disguise.disguisetypes.Disguise; +import me.libraryaddict.disguise.utilities.DisguiseUtilities; import me.libraryaddict.disguise.utilities.LibsMsg; import me.libraryaddict.disguise.utilities.parser.*; import me.libraryaddict.disguise.utilities.parser.params.ParamInfo; @@ -70,7 +71,7 @@ public class DisguiseModifyPlayerCommand extends DisguiseBaseCommand implements try { DisguiseParser.callMethods(sender, disguise, permissions, disguisePerm, new ArrayList<>(), - DisguiseParser.split(StringUtils.join(newArgs, " "))); + DisguiseUtilities.split(StringUtils.join(newArgs, " "))); } catch (DisguiseParseException ex) { if (ex.getMessage() != null) { diff --git a/src/main/java/me/libraryaddict/disguise/commands/DisguiseModifyRadiusCommand.java b/src/main/java/me/libraryaddict/disguise/commands/DisguiseModifyRadiusCommand.java index 75dcac67..a8b8b41a 100644 --- a/src/main/java/me/libraryaddict/disguise/commands/DisguiseModifyRadiusCommand.java +++ b/src/main/java/me/libraryaddict/disguise/commands/DisguiseModifyRadiusCommand.java @@ -3,6 +3,7 @@ package me.libraryaddict.disguise.commands; import me.libraryaddict.disguise.DisguiseAPI; import me.libraryaddict.disguise.disguisetypes.Disguise; import me.libraryaddict.disguise.disguisetypes.DisguiseType; +import me.libraryaddict.disguise.utilities.DisguiseUtilities; import me.libraryaddict.disguise.utilities.LibsMsg; import me.libraryaddict.disguise.utilities.TranslateType; import me.libraryaddict.disguise.utilities.parser.*; @@ -153,7 +154,7 @@ public class DisguiseModifyRadiusCommand extends DisguiseBaseCommand implements try { DisguiseParser.callMethods(sender, disguise, permissions, disguisePerm, new ArrayList<>(), - DisguiseParser.split(StringUtils.join(newArgs, " "))); + DisguiseUtilities.split(StringUtils.join(newArgs, " "))); modifiedDisguises++; } catch (DisguiseParseException ex) { diff --git a/src/main/java/me/libraryaddict/disguise/commands/DisguisePlayerCommand.java b/src/main/java/me/libraryaddict/disguise/commands/DisguisePlayerCommand.java index f5fb03c9..15337183 100644 --- a/src/main/java/me/libraryaddict/disguise/commands/DisguisePlayerCommand.java +++ b/src/main/java/me/libraryaddict/disguise/commands/DisguisePlayerCommand.java @@ -5,6 +5,7 @@ import me.libraryaddict.disguise.DisguiseConfig; import me.libraryaddict.disguise.disguisetypes.Disguise; import me.libraryaddict.disguise.disguisetypes.DisguiseType; import me.libraryaddict.disguise.disguisetypes.watchers.LivingWatcher; +import me.libraryaddict.disguise.utilities.DisguiseUtilities; import me.libraryaddict.disguise.utilities.LibsMsg; import me.libraryaddict.disguise.utilities.parser.*; import me.libraryaddict.disguise.utilities.parser.params.ParamInfo; @@ -72,7 +73,7 @@ public class DisguisePlayerCommand extends DisguiseBaseCommand implements TabCom try { disguise = DisguiseParser - .parseDisguise(sender, getPermNode(), DisguiseParser.split(StringUtils.join(newArgs, " ")), + .parseDisguise(sender, getPermNode(), DisguiseUtilities.split(StringUtils.join(newArgs, " ")), permissions); } catch (DisguiseParseException ex) { diff --git a/src/main/java/me/libraryaddict/disguise/commands/DisguiseRadiusCommand.java b/src/main/java/me/libraryaddict/disguise/commands/DisguiseRadiusCommand.java index 645ebf22..144be047 100644 --- a/src/main/java/me/libraryaddict/disguise/commands/DisguiseRadiusCommand.java +++ b/src/main/java/me/libraryaddict/disguise/commands/DisguiseRadiusCommand.java @@ -6,6 +6,7 @@ import me.libraryaddict.disguise.disguisetypes.Disguise; import me.libraryaddict.disguise.disguisetypes.DisguiseType; import me.libraryaddict.disguise.disguisetypes.watchers.LivingWatcher; import me.libraryaddict.disguise.utilities.ClassGetter; +import me.libraryaddict.disguise.utilities.DisguiseUtilities; import me.libraryaddict.disguise.utilities.LibsMsg; import me.libraryaddict.disguise.utilities.TranslateType; import me.libraryaddict.disguise.utilities.parser.*; @@ -134,7 +135,7 @@ public class DisguiseRadiusCommand extends DisguiseBaseCommand implements TabCom try { disguise = DisguiseParser - .parseDisguise(sender, getPermNode(), DisguiseParser.split(StringUtils.join(newArgs, " ")), + .parseDisguise(sender, getPermNode(), DisguiseUtilities.split(StringUtils.join(newArgs, " ")), permissions); } catch (DisguiseParseException ex) { diff --git a/src/main/java/me/libraryaddict/disguise/utilities/DisguiseUtilities.java b/src/main/java/me/libraryaddict/disguise/utilities/DisguiseUtilities.java index ae0a03a3..ecac9de4 100644 --- a/src/main/java/me/libraryaddict/disguise/utilities/DisguiseUtilities.java +++ b/src/main/java/me/libraryaddict/disguise/utilities/DisguiseUtilities.java @@ -51,6 +51,7 @@ import java.io.PrintWriter; import java.lang.reflect.*; import java.util.*; import java.util.logging.Logger; +import java.util.regex.Matcher; import java.util.regex.Pattern; public class DisguiseUtilities { @@ -1379,6 +1380,99 @@ public class DisguiseUtilities { } } + /** + * Splits a string while respecting quotes. + *

+ * Re + */ + /*public static String[] split(String string) { + Matcher matcher = Pattern.compile("\"(?:\"(?=\\S)|\\\\\"|[^\"])*(?:[^\\\\]\"(?=\\s|$))|\\S+").matcher(string); + + List list = new ArrayList<>(); + + while (matcher.find()) { + String match = matcher.group(); + + // If the match was quoted, then remove quotes and escapes + if (match.matches("\"(?:\"(?=\\S)|\\\\\"|[^\"])*(?:[^\\\\]\")")) { + // Replace the match by removing first and last quote + // Then remove escaped slashes from the trailing with regex + match = match.substring(1, match.length() - 1).replaceAll("\\\\\\\\(?=(\\\\\\\\)*$)", "\\"); + } + + list.add(matcher.group()); + } + + return list.toArray(new String[0]); + }*/ + public static String[] split(String string) { + // Regex where we first match any character that isn't a slash, if it is a slash then it must not have more + // slashes until it hits the quote + // If any slashes before the quote, they must be escaped. That is, two of them. + // Must end with a quote + Pattern endsWithQuote = Pattern.compile("^([^\\\\]|\\\\(?!\\\\*\"$))*(\\\\\\\\)*\"$"); + // Matches \"message quote, and + Pattern removeSlashes = Pattern.compile("^\\\\(\")|\\\\(?:(\\\\)(?=\\\\*\"$)|(\")$)"); + + List list = new ArrayList<>(); + String[] split = string.split(" "); + String[] unescapedSplit = new String[split.length]; + + loop: + for (int i = 0; i < split.length; i++) { + // If the word starts with a quote + if (split[i].startsWith("\"")) { + // Look for a word with an ending quote + for (int a = i; a < split.length; a++) { + // If it's the same word, but only one possible quote + if (a == i && split[i].length() == 1) { + continue; + } + + // Does not end with a valid quote + if (!endsWithQuote.matcher(split[a]).matches()) { + continue; + } + + // Found a sentence, build it + StringBuilder builder = new StringBuilder(); + + for (int b = i; b <= a; b++) { + Matcher matcher = removeSlashes.matcher(split[b]); + + // Remove any escapes for escaped quotes + String word = matcher.replaceAll("$1$2$3"); + + // If this is the beginning or end of a quote + if (b == i || b == a) { + // Remove the quote + word = word.substring(b == i ? 1 : 0, word.length() - (b == a ? 1 : 0)); + } + + if (b > i) { + builder.append(" "); + } + + builder.append(word); + } + + list.add(builder.toString()); + i = a; + continue loop; + } + } + + // Remove escapes if there, and add as a single word + Matcher matcher = removeSlashes.matcher(split[i]); + + String word = matcher.replaceAll("$1$2$3"); + + list.add(word); + } + + return list.toArray(new String[0]); + } + /** * Sends the self disguise to the player */ diff --git a/src/main/java/me/libraryaddict/disguise/utilities/parser/DisguiseParser.java b/src/main/java/me/libraryaddict/disguise/utilities/parser/DisguiseParser.java index a5f4e6d5..8c6980a9 100644 --- a/src/main/java/me/libraryaddict/disguise/utilities/parser/DisguiseParser.java +++ b/src/main/java/me/libraryaddict/disguise/utilities/parser/DisguiseParser.java @@ -17,8 +17,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; import java.util.Map.Entry; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class DisguiseParser { private static void doCheck(CommandSender sender, DisguisePermissions permissions, DisguisePerm disguisePerm, @@ -122,21 +120,6 @@ public class DisguiseParser { } } - /** - * Splits a string while respecting quotes - */ - public static String[] split(String string) { - Matcher matcher = Pattern.compile("\"(?:\"(?=\\S)|\\\\\"|[^\"])*(?:[^\\\\]\"(?=\\s|$))|\\S+").matcher(string); - - List list = new ArrayList<>(); - - while (matcher.find()) { - list.add(matcher.group()); - } - - return list.toArray(new String[0]); - } - /** * Returns the disguise if it all parsed correctly. Returns a exception with a complete message if it didn't. The * commandsender is purely used for checking permissions. Would defeat the purpose otherwise. To reach this diff --git a/src/test/java/me/libraryaddict/disguise/utilities/DisguiseUtilitiesTest.java b/src/test/java/me/libraryaddict/disguise/utilities/DisguiseUtilitiesTest.java new file mode 100644 index 00000000..d1b4f24b --- /dev/null +++ b/src/test/java/me/libraryaddict/disguise/utilities/DisguiseUtilitiesTest.java @@ -0,0 +1,120 @@ +package me.libraryaddict.disguise.utilities; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; + +/** + * Created by libraryaddict on 25/10/2018. + */ +public class DisguiseUtilitiesTest { + @Test + public void testQuoteSplitter() { + // Test if splits are correct + Assert.assertArrayEquals(new String[]{"A", "simple", "string"}, DisguiseUtilities.split("A simple string")); + + Assert.assertArrayEquals(new String[]{"A quoted string"}, DisguiseUtilities.split("\"A quoted string\"")); + + Assert.assertArrayEquals(new String[]{"\"A double quoted string\""}, + DisguiseUtilities.split("\"\"A double quoted string\"\"")); + + Assert.assertArrayEquals(new String[]{"A", "string", "containing a", "quote"}, + DisguiseUtilities.split("A string \"containing a\" quote")); + + Assert.assertArrayEquals(new String[]{"A", "string", "fully", "split"}, + DisguiseUtilities.split("\"A\" string fully split")); + + Assert.assertArrayEquals(new String[]{"A", "string", "fully", "split"}, + DisguiseUtilities.split("\"A\" \"string\" fully split")); + + Assert.assertArrayEquals(new String[]{"A", "string", "fully", "split"}, + DisguiseUtilities.split("A \"string\" fully split")); + + // Test if quotes are ignored properly and included in result + Assert.assertArrayEquals(new String[]{"A", "\"string", "fully", "split"}, + DisguiseUtilities.split("A \"string fully split")); + + Assert.assertArrayEquals(new String[]{"A", "\"string", "\"fully", "split"}, + DisguiseUtilities.split("A \"string \"fully split")); + + Assert.assertArrayEquals(new String[]{"\"A", "\"string", "\"fully", "split"}, + DisguiseUtilities.split("\"A \"string \"fully split")); + + Assert.assertArrayEquals(new String[]{"A", "string\"", "fully", "split"}, + DisguiseUtilities.split("A string\" fully split")); + + Assert.assertArrayEquals(new String[]{"A", "string\"", "fully\"", "split"}, + DisguiseUtilities.split("A string\" fully\" split")); + + Assert.assertArrayEquals(new String[]{"A", "string", "fully\"", "split"}, + DisguiseUtilities.split("A \"string\" fully\" split")); + + Assert.assertArrayEquals(new String[]{"A \"string", "with", "four", "splits"}, + DisguiseUtilities.split("\"A \"string\" with four splits")); + + // Test for quotes inside words + Assert.assertArrayEquals(new String[]{"Fully", "split", "\"", "message"}, + DisguiseUtilities.split("Fully split \"\"\" message")); + + // Test to make sure space can be quoted, with an empty quote at the end + Assert.assertArrayEquals(new String[]{" ", "\""}, DisguiseUtilities.split("\" \" \"")); + + // Test to make sure empty quotes, are still quotes + Assert.assertArrayEquals(new String[]{"Three", "", "split"}, DisguiseUtilities.split("Three \"\" split")); + + // Test to ensure single quotes, are still not quotes + Assert.assertArrayEquals(new String[]{"'Three", "split", "message'"}, + DisguiseUtilities.split("'Three split message'")); + + // There is a quoted message inside the quoted message, however it was not escaped + Assert.assertArrayEquals(new String[]{"A", "quoted message \"inside a quoted message\""}, + DisguiseUtilities.split("A \"quoted message \"inside a quoted message\"\"")); + + // Now test for escaped quotes, however as escaped quotes look different inside editors, I'll be replacing \ + // with / and " with ' + + // Test for escaped quotes, they should be ignored + splitEquals("/'Escaped quotes/'", "'Escaped", "quotes'"); + + // Test with one quote escaped + splitEquals("'Escaped quotes/'", "'Escaped", "quotes'"); + + // Test with no quotes escaped, where the escape was escaped + splitEquals("'Unescaped quotes/'", "'Unescaped", "quotes'"); + + // Test with three escaped slashes, then unescaped quote + splitEquals("'Unescaped quotes//////'", "Unescaped quotes///"); + + // Test with three escaped slashes, then escaped quote + splitEquals("'Escaped quotes///////'", "'Escaped", "quotes///'"); + + // Test with strings of escapes and quotes only + splitEquals("////", "////"); + + splitEquals("////'", "//'"); + + splitEquals("'////'", "//"); + + splitEquals("'/////'", "'//'"); + + splitEquals("'// //'", "// /"); + + splitEquals("'//// ////'", "//// //"); + + splitEquals( + "Foobar is not 'Foo Bar' but is a single word 'foobar' or as some quote it, /'foobar/' and again, " + + "not /'foo bar/' - It is 'foobar'!", + + "Foobar", "is", "not", "Foo Bar", "but", "is", "a", "single", "word", "foobar", "or", "as", "some", + "quote", "it,", "'foobar'", "and", "again,", "not", "'foo", "bar'", "-", "It", "is", "'foobar'!"); + } + + private void splitEquals(String toSplit, String... expected) { + String[] splitted = DisguiseUtilities.split(toSplit.replace("/", "\\").replace("'", "\"")); + String[] expect = Arrays.stream(expected).map(string -> string.replace("/", "\\").replace("'", "\"")) + .toArray(String[]::new); + + Assert.assertArrayEquals(expect, splitted); + } +}