From 34b64d8628b9e523734c48e6bc1255190ce985b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 22:54:19 +0000 Subject: [PATCH 001/111] Bump guava from 31.1-jre to 32.0.0-jre Bumps [guava](https://github.com/google/guava) from 31.1-jre to 32.0.0-jre. - [Release notes](https://github.com/google/guava/releases) - [Commits](https://github.com/google/guava/commits) --- updated-dependencies: - dependency-name: com.google.guava:guava dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index df433c6f..b452e4ed 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ com.google.guava guava - 31.1-jre + 32.0.0-jre From a7cf089b217c61b2cc23c0e88ae8778728f8b510 Mon Sep 17 00:00:00 2001 From: Christian Schierle Date: Thu, 13 Jul 2023 17:05:17 +0200 Subject: [PATCH 002/111] Adjusts regex to only match valid html tags starting with letters Fixes: OX-10173 --- src/main/java/sirius/kernel/commons/Strings.java | 2 +- src/test/java/sirius/kernel/commons/StringsTest.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/sirius/kernel/commons/Strings.java b/src/main/java/sirius/kernel/commons/Strings.java index 643137f0..30b12d37 100644 --- a/src/main/java/sirius/kernel/commons/Strings.java +++ b/src/main/java/sirius/kernel/commons/Strings.java @@ -575,7 +575,7 @@ public static String cleanup(@Nullable String inputString, @Nonnull Iterable]*>"); + private static final Pattern DETECT_XML_REGEX = Pattern.compile("<[a-zA-Z][a-zA-Z0-9]*[^>]*>"); /** * Determines if the given content contains XML tags. diff --git a/src/test/java/sirius/kernel/commons/StringsTest.java b/src/test/java/sirius/kernel/commons/StringsTest.java index e1461f0e..1b96d347 100644 --- a/src/test/java/sirius/kernel/commons/StringsTest.java +++ b/src/test/java/sirius/kernel/commons/StringsTest.java @@ -181,6 +181,7 @@ void probablyContainsXml() { assertTrue(Strings.probablyContainsXml("
")); assertTrue(Strings.probablyContainsXml("
")); assertFalse(Strings.probablyContainsXml("foo having < 3 m, with >= 3 m")); + assertFalse(Strings.probablyContainsXml("foo length<19. with width > 80")); } @Test From a94cb6730dbbb12892ff0383b1d7b12b1a4585d9 Mon Sep 17 00:00:00 2001 From: Christian Schierle Date: Wed, 19 Jul 2023 08:32:27 +0200 Subject: [PATCH 003/111] Adds detection and cleanups for html texts Fixes: OX-10173 --- .../sirius/kernel/commons/StringCleanup.java | 135 +++++++++++++++++- .../java/sirius/kernel/commons/Strings.java | 16 +++ .../sirius/kernel/commons/StringsTest.java | 31 +++- 3 files changed, 176 insertions(+), 6 deletions(-) diff --git a/src/main/java/sirius/kernel/commons/StringCleanup.java b/src/main/java/sirius/kernel/commons/StringCleanup.java index c6b48aba..a618de4d 100644 --- a/src/main/java/sirius/kernel/commons/StringCleanup.java +++ b/src/main/java/sirius/kernel/commons/StringCleanup.java @@ -8,6 +8,8 @@ package sirius.kernel.commons; +import sirius.kernel.health.Exceptions; + import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.text.CharacterIterator; @@ -30,11 +32,22 @@ public class StringCleanup { private static final Pattern PATTERN_CONTROL_CHARACTERS = Pattern.compile("\\p{Cntrl}"); private static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\s"); private static final Pattern PATTERN_WHITESPACES = Pattern.compile("\\s+"); + private static final Pattern PATTERN_NBSP_CHARACTERS = Pattern.compile("\u00A0+"); + private static final Pattern PATTERN_LINEBREAKS = Pattern.compile("\\r?\\n"); private static final Pattern PATTERN_PUNCTUATION = Pattern.compile("\\p{Punct}"); private static final Pattern PATTERN_NON_ALPHA_NUMERIC = Pattern.compile("([^\\p{L}\\d])"); private static final Pattern PATTERN_NON_LETTER = Pattern.compile("\\P{L}"); private static final Pattern PATTERN_NON_DIGIT = Pattern.compile("\\D"); - private static final Pattern STRIP_XML_REGEX = Pattern.compile("\\s*]*>\\s*"); + private static final Pattern PATTERN_DECIMAL_ENTITY = Pattern.compile("&(\\d+);"); + private static final Pattern PATTERN_HEX_ENTITY = Pattern.compile("&x0*([0-9a-fA-F]+);"); + private static final Pattern PATTERN_BR_TAG = Pattern.compile("<(br|BR) */? *>"); + private static final Pattern PATTERN_LILI_TAG = Pattern.compile("<(/li|/LI)>\\r?\\n?\\t?<(li|LI)>"); + private static final Pattern PATTERN_LI_TAG = Pattern.compile("<(/?li|/?LI)>"); + private static final Pattern PATTERN_PP_TAG = Pattern.compile("<(/p|/P)>\\r?\\n?\\t?<([pP])>"); + private static final Pattern PATTERN_P_TAG = Pattern.compile("<(/?p|/?P)>"); + private static final Pattern STRIP_HTML_REGEX = Pattern.compile("]*>"); + + private static final Pattern STRIP_XML_REGEX = Pattern.compile("\\s*]*>\\s*"); private static final Map unicodeMapping = new TreeMap<>(); @@ -277,6 +290,29 @@ public static String reduceWhitespace(@Nonnull String input) { return PATTERN_WHITESPACES.matcher(input).replaceAll(" "); } + /** + * Replaces the special whitespace character {@code " "}({@linkplain #PATTERN_NBSP_CHARACTERS Unicode: u00A0})) + * by simple spaces. + * + * @param input the input to process + * @return the resulting string + */ + @Nonnull + public static String reduceNbspCharacters(@Nonnull String input) { + return PATTERN_NBSP_CHARACTERS.matcher(input).replaceAll(" "); + } + + /** + * Replaces all {@linkplain #PATTERN_LINEBREAKS line breaks} with a tab (\t) character. + * + * @param input the input to process + * @return the resulting string + */ + @Nonnull + public static String replaceLinebreaksWithTabs(@Nonnull String input) { + return PATTERN_LINEBREAKS.matcher(input).replaceAll("\t"); + } + /** * Trims the given string. *

@@ -316,6 +352,100 @@ public static String uppercase(@Nonnull String input) { return input.toUpperCase(); } + /** + * Removes all {@linkplain #STRIP_HTML_REGEX html tags} from the given string. + * + * @param input the input to process + * @return the resulting string + */ + @Nonnull + public static String removeHtmlTags(@Nonnull String input) { + return STRIP_HTML_REGEX.matcher(input).replaceAll(""); + } + + /** + * Resolves encoded HTML entities to their plain text equivalent in the given string. + * + * @param input the input to process + * @return the resulting string + */ + @Nonnull + public static String decodeHtmlEntities(@Nonnull String input) { + input = input.replace(" ", " ") + .replace("ä", "ä") + .replace("ö", "ö") + .replace("ü", "ü") + .replace("Ä", "Ä") + .replace("Ö", "Ö") + .replace("Ü", "Ü") + .replace("ß", "ß") + .replace("<", "<") + .replace(">", ">") + .replace(""", "\"") + .replace("'", "'") + .replace("&", "&") + .replace("• ", "* ") + .replace("•", "* ") + .replace("‣ ", "* ") + .replace("‣", "* ") + .replace("⁃ ", "* ") + .replace("⁃", "* "); + input = Strings.replaceAll(PATTERN_DECIMAL_ENTITY, input, s -> { + try { + return String.valueOf(Character.toChars(Integer.parseInt(s))); + } catch (NumberFormatException e) { + Exceptions.ignore(e); + } catch (Exception e) { + Exceptions.handle(e); + } + return ""; + }); + input = Strings.replaceAll(PATTERN_HEX_ENTITY, input, s -> { + try { + return String.valueOf(Character.toChars(Integer.parseInt(s, 16))); + } catch (NumberFormatException e) { + Exceptions.ignore(e); + } catch (Exception e) { + Exceptions.handle(e); + } + return ""; + }); + + return input; + } + + /** + * Normalizes a text by removing all HTML, entities and special characters. + * + * @param input the input to process + * @return the resulting string + */ + @Nonnull + public static String htmlToPlainText(@Nonnull String input) { + String normalizedText = input; + + if (STRIP_HTML_REGEX.matcher(normalizedText).find()) { + // It is HTML -> replace line breaks with tabs + normalizedText = Strings.cleanup(normalizedText, StringCleanup::replaceLinebreaksWithTabs); + // Replace br tags with line breaks + normalizedText = PATTERN_BR_TAG.matcher(normalizedText).replaceAll("\n"); + // Replace li tags with line breaks + normalizedText = PATTERN_LILI_TAG.matcher(normalizedText).replaceAll("\n"); + normalizedText = PATTERN_LI_TAG.matcher(normalizedText).replaceAll("\n"); + // Replace p tags with line breaks + normalizedText = PATTERN_PP_TAG.matcher(normalizedText).replaceAll("\n"); + normalizedText = PATTERN_P_TAG.matcher(normalizedText).replaceAll("\n"); + // Remove any other tags + normalizedText = Strings.cleanup(normalizedText, StringCleanup::removeHtmlTags); + // Convert all generated tabs to blanks and collapse multiple + normalizedText = Strings.cleanup(normalizedText, StringCleanup::reduceWhitespace); + // Decode entities + normalizedText = Strings.cleanup(normalizedText, StringCleanup::decodeHtmlEntities); + } + + return normalizedText; + } + /** * Removes all umlauts and other decorated latin characters. * @@ -371,7 +501,7 @@ public static String replaceXml(@Nullable String input) { } /** - * Escapes XML characters to that the given string can be safely embedded in XML. + * Escapes XML characters so that the given string can be safely embedded in XML. * * @param input the input to process * @return the resulting string @@ -415,6 +545,7 @@ public static String escapeXml(@Nullable String input) { * @param input the input to process * @return the resulting string */ + @Nullable public static String nlToBr(String input) { if (input == null) { return null; diff --git a/src/main/java/sirius/kernel/commons/Strings.java b/src/main/java/sirius/kernel/commons/Strings.java index 30b12d37..9b3c8733 100644 --- a/src/main/java/sirius/kernel/commons/Strings.java +++ b/src/main/java/sirius/kernel/commons/Strings.java @@ -591,6 +591,22 @@ public static boolean probablyContainsXml(@Nullable String content) { return DETECT_XML_REGEX.matcher(content).find(); } + private static final Pattern DETECT_HTML_REGEX = Pattern.compile("]*>"); + + /** + * Determines if the given content contains HTML tags. + * + * @param content the content to check + * @return true if HTML tags were found, false otherwise + */ + public static boolean probablyContainsHtml(@Nullable String content) { + if (Strings.isEmpty(content)) { + return false; + } + + return DETECT_HTML_REGEX.matcher(content).find(); + } + /** * Removes all umlauts and other decorated latin characters. * diff --git a/src/test/java/sirius/kernel/commons/StringsTest.java b/src/test/java/sirius/kernel/commons/StringsTest.java index 1b96d347..acf16ed5 100644 --- a/src/test/java/sirius/kernel/commons/StringsTest.java +++ b/src/test/java/sirius/kernel/commons/StringsTest.java @@ -166,12 +166,22 @@ void cleanup() { assertEquals("Hello", Strings.cleanup("Hel-lo", StringCleanup::removePunctuation)); assertEquals("Hello", Strings.cleanup("\10Hello", StringCleanup::removeControlCharacters)); assertEquals("Test", Strings.cleanup("Test", StringCleanup::replaceXml, StringCleanup::trim)); - assertEquals("Test", Strings.cleanup("Test", StringCleanup::replaceXml, StringCleanup::trim)); assertEquals("Test", Strings.cleanup("Test
", StringCleanup::replaceXml, StringCleanup::trim)); - assertEquals("Test Blubb", Strings.cleanup("Test
Blubb
", StringCleanup::replaceXml, StringCleanup::trim)); - assertEquals("foo having < 3 m, with >= 3 m", Strings.cleanup("foo having < 3 m, with >= 3 m", StringCleanup::replaceXml, StringCleanup::trim)); - assertEquals("<b>Foo <br /> Bar</b>", Strings.cleanup("Foo
Bar
", StringCleanup::escapeXml)); + assertEquals("Test Blubb", + Strings.cleanup("Test
Blubb
", StringCleanup::replaceXml, StringCleanup::trim)); + assertEquals("foo having < 3 m, with >= 3 m", + Strings.cleanup("foo having < 3 m, with >= 3 m", StringCleanup::replaceXml, StringCleanup::trim)); + assertEquals("<b>Foo <br /> Bar</b>", + Strings.cleanup("Foo
Bar
", StringCleanup::escapeXml)); assertEquals("Hello
World", Strings.cleanup("Hello\nWorld", StringCleanup::nlToBr)); + assertEquals("Hello World", Strings.cleanup("Hello  World", StringCleanup::reduceNbspCharacters)); + assertEquals("Hello\tWorld", Strings.cleanup("Hello\r\nWorld", StringCleanup::replaceLinebreaksWithTabs)); + assertEquals("Testalert('Hello World!')", + Strings.cleanup("Test", StringCleanup::removeHtmlTags)); + assertEquals(" äöüÄÖÜß<>\"'&* * * * * * ", + Strings.cleanup( + " äöüÄÖÜß<>"'&••‣‣⁃⁃", + StringCleanup::decodeHtmlEntities)); } @Test @@ -180,10 +190,23 @@ void probablyContainsXml() { assertTrue(Strings.probablyContainsXml("
")); assertTrue(Strings.probablyContainsXml("
")); assertTrue(Strings.probablyContainsXml("
")); + assertTrue(Strings.probablyContainsXml("")); assertFalse(Strings.probablyContainsXml("foo having < 3 m, with >= 3 m")); assertFalse(Strings.probablyContainsXml("foo length<19. with width > 80")); } + @Test + void probablyContainsHtml() { + assertTrue(Strings.probablyContainsHtml("Test")); + assertTrue(Strings.probablyContainsHtml("
")); + assertTrue(Strings.probablyContainsHtml("
")); + assertTrue(Strings.probablyContainsHtml("
")); + assertTrue(Strings.probablyContainsHtml("
")); + assertTrue(Strings.probablyContainsHtml("")); + assertFalse(Strings.probablyContainsHtml("foo having < 3 m, with >= 3 m")); + assertFalse(Strings.probablyContainsHtml("foo length<19. with width > 80")); + } + @Test void limit() { assertEquals("", Strings.limit(null, 10, false)); From c441f89aee42ed4a9ce3655fc699f9f101752eb1 Mon Sep 17 00:00:00 2001 From: Christian Schierle Date: Wed, 19 Jul 2023 08:48:57 +0200 Subject: [PATCH 004/111] Also tests reduction of tabs Fixes: OX-10173 --- src/test/java/sirius/kernel/commons/StringsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/sirius/kernel/commons/StringsTest.java b/src/test/java/sirius/kernel/commons/StringsTest.java index acf16ed5..7055f6ab 100644 --- a/src/test/java/sirius/kernel/commons/StringsTest.java +++ b/src/test/java/sirius/kernel/commons/StringsTest.java @@ -158,7 +158,7 @@ void reduceCharacters() { @Test void cleanup() { assertEquals("Hel lo", Strings.cleanup("Hel lo ", StringCleanup::trim)); - assertEquals("Hel lo ", Strings.cleanup("Hel lo ", StringCleanup::reduceWhitespace)); + assertEquals("Hel lo ", Strings.cleanup("Hel \t\t lo ", StringCleanup::reduceWhitespace)); assertEquals("Hello", Strings.cleanup("Hel lo", StringCleanup::removeWhitespace)); assertEquals("Hello", Strings.cleanup("Héllo", StringCleanup::reduceCharacters)); assertEquals("hello", Strings.cleanup("Héllo", StringCleanup::reduceCharacters, StringCleanup::lowercase)); From 9c304b204d091a4e625877f65b89e6b3c6f02848 Mon Sep 17 00:00:00 2001 From: Christian Schierle Date: Wed, 19 Jul 2023 10:46:43 +0200 Subject: [PATCH 005/111] Simplifies XML regex. A tag starts with a letter followed by any character except the closing bracket, so the additional character group [a-zA-Z0-9] was unnecessary. Fixes: OX-10173 --- src/main/java/sirius/kernel/commons/Strings.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/sirius/kernel/commons/Strings.java b/src/main/java/sirius/kernel/commons/Strings.java index 9b3c8733..32f7ee35 100644 --- a/src/main/java/sirius/kernel/commons/Strings.java +++ b/src/main/java/sirius/kernel/commons/Strings.java @@ -575,7 +575,7 @@ public static String cleanup(@Nullable String inputString, @Nonnull Iterable]*>"); + private static final Pattern DETECT_XML_REGEX = Pattern.compile("<[a-zA-Z][^>]*>"); /** * Determines if the given content contains XML tags. From 198b1659498e89813ffe319e37c736d6ea84492f Mon Sep 17 00:00:00 2001 From: Christian Schierle Date: Wed, 19 Jul 2023 10:58:32 +0200 Subject: [PATCH 006/111] Also checks for closing tags by adding an optional slash to the regex. This makes the separate html check unnecessary. Fixes: OX-10173 --- .../java/sirius/kernel/commons/Strings.java | 18 +----------------- .../sirius/kernel/commons/StringsTest.java | 13 +------------ 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/src/main/java/sirius/kernel/commons/Strings.java b/src/main/java/sirius/kernel/commons/Strings.java index 32f7ee35..0a0e9bf3 100644 --- a/src/main/java/sirius/kernel/commons/Strings.java +++ b/src/main/java/sirius/kernel/commons/Strings.java @@ -575,7 +575,7 @@ public static String cleanup(@Nullable String inputString, @Nonnull Iterable]*>"); + private static final Pattern DETECT_XML_REGEX = Pattern.compile("]*>"); /** * Determines if the given content contains XML tags. @@ -591,22 +591,6 @@ public static boolean probablyContainsXml(@Nullable String content) { return DETECT_XML_REGEX.matcher(content).find(); } - private static final Pattern DETECT_HTML_REGEX = Pattern.compile("]*>"); - - /** - * Determines if the given content contains HTML tags. - * - * @param content the content to check - * @return true if HTML tags were found, false otherwise - */ - public static boolean probablyContainsHtml(@Nullable String content) { - if (Strings.isEmpty(content)) { - return false; - } - - return DETECT_HTML_REGEX.matcher(content).find(); - } - /** * Removes all umlauts and other decorated latin characters. * diff --git a/src/test/java/sirius/kernel/commons/StringsTest.java b/src/test/java/sirius/kernel/commons/StringsTest.java index 7055f6ab..04add2c2 100644 --- a/src/test/java/sirius/kernel/commons/StringsTest.java +++ b/src/test/java/sirius/kernel/commons/StringsTest.java @@ -190,23 +190,12 @@ void probablyContainsXml() { assertTrue(Strings.probablyContainsXml("
")); assertTrue(Strings.probablyContainsXml("
")); assertTrue(Strings.probablyContainsXml("
")); + assertTrue(Strings.probablyContainsXml("")); assertTrue(Strings.probablyContainsXml("")); assertFalse(Strings.probablyContainsXml("foo having < 3 m, with >= 3 m")); assertFalse(Strings.probablyContainsXml("foo length<19. with width > 80")); } - @Test - void probablyContainsHtml() { - assertTrue(Strings.probablyContainsHtml("Test")); - assertTrue(Strings.probablyContainsHtml("
")); - assertTrue(Strings.probablyContainsHtml("
")); - assertTrue(Strings.probablyContainsHtml("
")); - assertTrue(Strings.probablyContainsHtml("
")); - assertTrue(Strings.probablyContainsHtml("")); - assertFalse(Strings.probablyContainsHtml("foo having < 3 m, with >= 3 m")); - assertFalse(Strings.probablyContainsHtml("foo length<19. with width > 80")); - } - @Test void limit() { assertEquals("", Strings.limit(null, 10, false)); From e350ba5a4778420f1d74a562478dbe31dbd26f51 Mon Sep 17 00:00:00 2001 From: Christian Schierle Date: Wed, 19 Jul 2023 13:59:51 +0200 Subject: [PATCH 007/111] Refers to the XML detection regex to deduplicate pattern definition Fixes: OX-10173 --- src/main/java/sirius/kernel/commons/StringCleanup.java | 3 +-- src/main/java/sirius/kernel/commons/Strings.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/sirius/kernel/commons/StringCleanup.java b/src/main/java/sirius/kernel/commons/StringCleanup.java index a618de4d..a1965a25 100644 --- a/src/main/java/sirius/kernel/commons/StringCleanup.java +++ b/src/main/java/sirius/kernel/commons/StringCleanup.java @@ -47,8 +47,7 @@ public class StringCleanup { private static final Pattern PATTERN_P_TAG = Pattern.compile("<(/?p|/?P)>"); private static final Pattern STRIP_HTML_REGEX = Pattern.compile("]*>"); - private static final Pattern STRIP_XML_REGEX = Pattern.compile("\\s*]*>\\s*"); - + private static final Pattern STRIP_XML_REGEX = Pattern.compile("\\s*" + Strings.DETECT_XML_REGEX + "\\s*"); private static final Map unicodeMapping = new TreeMap<>(); static { diff --git a/src/main/java/sirius/kernel/commons/Strings.java b/src/main/java/sirius/kernel/commons/Strings.java index 0a0e9bf3..50d55901 100644 --- a/src/main/java/sirius/kernel/commons/Strings.java +++ b/src/main/java/sirius/kernel/commons/Strings.java @@ -575,7 +575,7 @@ public static String cleanup(@Nullable String inputString, @Nonnull Iterable]*>"); + protected static final Pattern DETECT_XML_REGEX = Pattern.compile("]*>"); /** * Determines if the given content contains XML tags. From acfb65a49a7ba14bc4cc844d29f987ddd9c7dba8 Mon Sep 17 00:00:00 2001 From: Christian Schierle Date: Wed, 19 Jul 2023 15:02:15 +0200 Subject: [PATCH 008/111] Extends whitespace regex such that it includes any kind of whitespace or invisible separator Fixes: OX-10173 --- .../sirius/kernel/commons/StringCleanup.java | 36 +++---------------- .../sirius/kernel/commons/StringsTest.java | 6 ++-- 2 files changed, 7 insertions(+), 35 deletions(-) diff --git a/src/main/java/sirius/kernel/commons/StringCleanup.java b/src/main/java/sirius/kernel/commons/StringCleanup.java index a1965a25..2a1abf9a 100644 --- a/src/main/java/sirius/kernel/commons/StringCleanup.java +++ b/src/main/java/sirius/kernel/commons/StringCleanup.java @@ -30,9 +30,8 @@ public class StringCleanup { private static final Pattern PATTERN_CONTROL_CHARACTERS = Pattern.compile("\\p{Cntrl}"); - private static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\s"); - private static final Pattern PATTERN_WHITESPACES = Pattern.compile("\\s+"); - private static final Pattern PATTERN_NBSP_CHARACTERS = Pattern.compile("\u00A0+"); + private static final Pattern PATTERN_WHITESPACE = Pattern.compile("[\\s\\p{Z}]"); + private static final Pattern PATTERN_WHITESPACES = Pattern.compile(PATTERN_WHITESPACE + "+"); private static final Pattern PATTERN_LINEBREAKS = Pattern.compile("\\r?\\n"); private static final Pattern PATTERN_PUNCTUATION = Pattern.compile("\\p{Punct}"); private static final Pattern PATTERN_NON_ALPHA_NUMERIC = Pattern.compile("([^\\p{L}\\d])"); @@ -289,29 +288,6 @@ public static String reduceWhitespace(@Nonnull String input) { return PATTERN_WHITESPACES.matcher(input).replaceAll(" "); } - /** - * Replaces the special whitespace character {@code " "}({@linkplain #PATTERN_NBSP_CHARACTERS Unicode: u00A0})) - * by simple spaces. - * - * @param input the input to process - * @return the resulting string - */ - @Nonnull - public static String reduceNbspCharacters(@Nonnull String input) { - return PATTERN_NBSP_CHARACTERS.matcher(input).replaceAll(" "); - } - - /** - * Replaces all {@linkplain #PATTERN_LINEBREAKS line breaks} with a tab (\t) character. - * - * @param input the input to process - * @return the resulting string - */ - @Nonnull - public static String replaceLinebreaksWithTabs(@Nonnull String input) { - return PATTERN_LINEBREAKS.matcher(input).replaceAll("\t"); - } - /** * Trims the given string. *

@@ -423,9 +399,9 @@ public static String decodeHtmlEntities(@Nonnull String input) { public static String htmlToPlainText(@Nonnull String input) { String normalizedText = input; - if (STRIP_HTML_REGEX.matcher(normalizedText).find()) { - // It is HTML -> replace line breaks with tabs - normalizedText = Strings.cleanup(normalizedText, StringCleanup::replaceLinebreaksWithTabs); + if (STRIP_XML_REGEX.matcher(normalizedText).find()) { + // Reduce all contained whitespaces, tabs, and line breaks + normalizedText = Strings.cleanup(normalizedText, StringCleanup::reduceWhitespace); // Replace br tags with line breaks normalizedText = PATTERN_BR_TAG.matcher(normalizedText).replaceAll("\n"); // Replace li tags with line breaks @@ -436,8 +412,6 @@ public static String htmlToPlainText(@Nonnull String input) { normalizedText = PATTERN_P_TAG.matcher(normalizedText).replaceAll("\n"); // Remove any other tags normalizedText = Strings.cleanup(normalizedText, StringCleanup::removeHtmlTags); - // Convert all generated tabs to blanks and collapse multiple - normalizedText = Strings.cleanup(normalizedText, StringCleanup::reduceWhitespace); // Decode entities normalizedText = Strings.cleanup(normalizedText, StringCleanup::decodeHtmlEntities); } diff --git a/src/test/java/sirius/kernel/commons/StringsTest.java b/src/test/java/sirius/kernel/commons/StringsTest.java index 04add2c2..9283f16f 100644 --- a/src/test/java/sirius/kernel/commons/StringsTest.java +++ b/src/test/java/sirius/kernel/commons/StringsTest.java @@ -158,8 +158,8 @@ void reduceCharacters() { @Test void cleanup() { assertEquals("Hel lo", Strings.cleanup("Hel lo ", StringCleanup::trim)); - assertEquals("Hel lo ", Strings.cleanup("Hel \t\t lo ", StringCleanup::reduceWhitespace)); - assertEquals("Hello", Strings.cleanup("Hel lo", StringCleanup::removeWhitespace)); + assertEquals("Hel lo ", Strings.cleanup("Hel \t \t \r\n lo ", StringCleanup::reduceWhitespace)); + assertEquals("Hello", Strings.cleanup("Hel \t \t \n lo ", StringCleanup::removeWhitespace)); assertEquals("Hello", Strings.cleanup("Héllo", StringCleanup::reduceCharacters)); assertEquals("hello", Strings.cleanup("Héllo", StringCleanup::reduceCharacters, StringCleanup::lowercase)); assertEquals("HELLO", Strings.cleanup("Héllo", StringCleanup::reduceCharacters, StringCleanup::uppercase)); @@ -174,8 +174,6 @@ void cleanup() { assertEquals("<b>Foo <br /> Bar</b>", Strings.cleanup("Foo
Bar
", StringCleanup::escapeXml)); assertEquals("Hello
World", Strings.cleanup("Hello\nWorld", StringCleanup::nlToBr)); - assertEquals("Hello World", Strings.cleanup("Hello  World", StringCleanup::reduceNbspCharacters)); - assertEquals("Hello\tWorld", Strings.cleanup("Hello\r\nWorld", StringCleanup::replaceLinebreaksWithTabs)); assertEquals("Testalert('Hello World!')", Strings.cleanup("Test", StringCleanup::removeHtmlTags)); assertEquals(" äöüÄÖÜß<>\"'&* * * * * * ", From f7e6c30bb484eed7568f44e166dd28eebcc6a6b5 Mon Sep 17 00:00:00 2001 From: Christian Schierle Date: Wed, 19 Jul 2023 15:02:32 +0200 Subject: [PATCH 009/111] Defines and detects a list of allowed html tags Fixes: OX-10173 --- .../sirius/kernel/commons/StringCleanup.java | 78 +++++++++++++++++-- .../java/sirius/kernel/commons/Strings.java | 18 +++++ .../sirius/kernel/commons/StringsTest.java | 17 +++- 3 files changed, 107 insertions(+), 6 deletions(-) diff --git a/src/main/java/sirius/kernel/commons/StringCleanup.java b/src/main/java/sirius/kernel/commons/StringCleanup.java index 2a1abf9a..b2a81be5 100644 --- a/src/main/java/sirius/kernel/commons/StringCleanup.java +++ b/src/main/java/sirius/kernel/commons/StringCleanup.java @@ -14,6 +14,7 @@ import javax.annotation.Nullable; import java.text.CharacterIterator; import java.text.StringCharacterIterator; +import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.function.UnaryOperator; @@ -44,7 +45,60 @@ public class StringCleanup { private static final Pattern PATTERN_LI_TAG = Pattern.compile("<(/?li|/?LI)>"); private static final Pattern PATTERN_PP_TAG = Pattern.compile("<(/p|/P)>\\r?\\n?\\t?<([pP])>"); private static final Pattern PATTERN_P_TAG = Pattern.compile("<(/?p|/?P)>"); - private static final Pattern STRIP_HTML_REGEX = Pattern.compile("]*>"); + + public static final String TAG_P = "p"; + public static final String TAG_BR = "br"; + public static final String TAG_DIV = "div"; + public static final String TAG_SPAN = "span"; + public static final String TAG_SMALL = "small"; + public static final String TAG_H1 = "h1"; + public static final String TAG_H2 = "h2"; + public static final String TAG_H3 = "h3"; + public static final String TAG_H4 = "h4"; + public static final String TAG_H5 = "h5"; + public static final String TAG_H6 = "h6"; + public static final String TAG_B = "b"; + public static final String TAG_STRONG = "strong"; + public static final String TAG_I = "i"; + public static final String TAG_EM = "em"; + public static final String TAG_U = "u"; + public static final String TAG_SUP = "sup"; + public static final String TAG_SUB = "sub"; + public static final String TAG_MARK = "mark"; + public static final String TAG_HR = "hr"; + public static final String TAG_DL = "dl"; + public static final String TAG_DT = "dt"; + public static final String TAG_DD = "dd"; + public static final String TAG_OL = "ol"; + public static final String TAG_UL = "ul"; + public static final String TAG_LI = "li"; + + public static final List ALLOWED_HTML_TAG_NAMES = List.of(TAG_P, + TAG_BR, + TAG_DIV, + TAG_SPAN, + TAG_SMALL, + TAG_H1, + TAG_H2, + TAG_H3, + TAG_H4, + TAG_H5, + TAG_H6, + TAG_B, + TAG_STRONG, + TAG_I, + TAG_EM, + TAG_U, + TAG_SUP, + TAG_SUB, + TAG_MARK, + TAG_HR, + TAG_DL, + TAG_DT, + TAG_DD, + TAG_OL, + TAG_UL, + TAG_LI); private static final Pattern STRIP_XML_REGEX = Pattern.compile("\\s*" + Strings.DETECT_XML_REGEX + "\\s*"); private static final Map unicodeMapping = new TreeMap<>(); @@ -328,14 +382,28 @@ public static String uppercase(@Nonnull String input) { } /** - * Removes all {@linkplain #STRIP_HTML_REGEX html tags} from the given string. + * Removes all {@linkplain #STRIP_XML_REGEX XML tags} from the given string. + * + * @param input the input to process + * @return the resulting string + */ + @Nonnull + public static String removeXml(@Nonnull String input) { + return STRIP_XML_REGEX.matcher(input).replaceAll(""); + } + + /** + * Removes all unsafe HTML tags from the given string. * * @param input the input to process * @return the resulting string */ @Nonnull - public static String removeHtmlTags(@Nonnull String input) { - return STRIP_HTML_REGEX.matcher(input).replaceAll(""); + public static String removeUnsafeHtml(@Nonnull String input) { + return STRIP_XML_REGEX.matcher(input) + .replaceAll(match -> Strings.DETECT_ALLOWED_HTML_REGEX.matcher(match.group()).matches() ? + match.group() : + ""); } /** @@ -411,7 +479,7 @@ public static String htmlToPlainText(@Nonnull String input) { normalizedText = PATTERN_PP_TAG.matcher(normalizedText).replaceAll("\n"); normalizedText = PATTERN_P_TAG.matcher(normalizedText).replaceAll("\n"); // Remove any other tags - normalizedText = Strings.cleanup(normalizedText, StringCleanup::removeHtmlTags); + normalizedText = Strings.cleanup(normalizedText, StringCleanup::removeXml); // Decode entities normalizedText = Strings.cleanup(normalizedText, StringCleanup::decodeHtmlEntities); } diff --git a/src/main/java/sirius/kernel/commons/Strings.java b/src/main/java/sirius/kernel/commons/Strings.java index 50d55901..c5569690 100644 --- a/src/main/java/sirius/kernel/commons/Strings.java +++ b/src/main/java/sirius/kernel/commons/Strings.java @@ -591,6 +591,24 @@ public static boolean probablyContainsXml(@Nullable String content) { return DETECT_XML_REGEX.matcher(content).find(); } + protected static final Pattern DETECT_ALLOWED_HTML_REGEX = + Pattern.compile("]*>"); + + /** + * Determines if the given content contains HTML tags that are {@linkplain #DETECT_ALLOWED_HTML_REGEX allowed} in + * the system. + * + * @param content the content to check + * @return true if allowed HTML tags were found, false otherwise + */ + public static boolean containsAllowedHtml(@Nullable String content) { + if (Strings.isEmpty(content)) { + return false; + } + + return DETECT_ALLOWED_HTML_REGEX.matcher(content).find(); + } + /** * Removes all umlauts and other decorated latin characters. * diff --git a/src/test/java/sirius/kernel/commons/StringsTest.java b/src/test/java/sirius/kernel/commons/StringsTest.java index 9283f16f..917e95c9 100644 --- a/src/test/java/sirius/kernel/commons/StringsTest.java +++ b/src/test/java/sirius/kernel/commons/StringsTest.java @@ -175,7 +175,9 @@ void cleanup() { Strings.cleanup("Foo
Bar
", StringCleanup::escapeXml)); assertEquals("Hello
World", Strings.cleanup("Hello\nWorld", StringCleanup::nlToBr)); assertEquals("Testalert('Hello World!')", - Strings.cleanup("Test", StringCleanup::removeHtmlTags)); + Strings.cleanup("Test", StringCleanup::removeXml)); + assertEquals("

Test
", + Strings.cleanup("
", StringCleanup::removeUnsafeHtml)); assertEquals(" äöüÄÖÜß<>\"'&* * * * * * ", Strings.cleanup( " äöüÄÖÜß<>"'&••‣‣⁃⁃", @@ -194,6 +196,19 @@ void probablyContainsXml() { assertFalse(Strings.probablyContainsXml("foo length<19. with width > 80")); } + @Test + void containsAllowedHtml() { + assertTrue(Strings.containsAllowedHtml("Test")); + assertTrue(Strings.containsAllowedHtml("
")); + assertTrue(Strings.containsAllowedHtml("
")); + assertTrue(Strings.containsAllowedHtml("
")); + assertTrue(Strings.containsAllowedHtml("")); + assertFalse(Strings.containsAllowedHtml("")); + assertFalse(Strings.containsAllowedHtml("Test ")); + assertFalse(Strings.containsAllowedHtml("foo having < 3 m, with >= 3 m")); + assertFalse(Strings.containsAllowedHtml("foo length<19. with width > 80")); + } + @Test void limit() { assertEquals("", Strings.limit(null, 10, false)); From 137211a90677e4a66ce714afe09aff939ab30675 Mon Sep 17 00:00:00 2001 From: Christian Schierle Date: Wed, 19 Jul 2023 15:39:36 +0200 Subject: [PATCH 010/111] Adds test that the allowed HTML check does not restrict to only allowed tags. The method is intended as a check if the text should be further processed, or if it can be escaped as a whole since it only contains unsafe tags. Fixes: OX-10173 --- src/test/java/sirius/kernel/commons/StringsTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/sirius/kernel/commons/StringsTest.java b/src/test/java/sirius/kernel/commons/StringsTest.java index 917e95c9..11769630 100644 --- a/src/test/java/sirius/kernel/commons/StringsTest.java +++ b/src/test/java/sirius/kernel/commons/StringsTest.java @@ -203,6 +203,7 @@ void containsAllowedHtml() { assertTrue(Strings.containsAllowedHtml("
")); assertTrue(Strings.containsAllowedHtml("
")); assertTrue(Strings.containsAllowedHtml("")); + assertTrue(Strings.containsAllowedHtml("
")); assertFalse(Strings.containsAllowedHtml("")); assertFalse(Strings.containsAllowedHtml("Test ")); assertFalse(Strings.containsAllowedHtml("foo having < 3 m, with >= 3 m")); From c6ebd8a6a161d00b8d29f59021e654130eff4d5a Mon Sep 17 00:00:00 2001 From: Christian Schierle Date: Wed, 19 Jul 2023 15:42:11 +0200 Subject: [PATCH 011/111] Removes unused pattern. This is included in whitespace patterns. Fixes: OX-10173 --- src/main/java/sirius/kernel/commons/StringCleanup.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/sirius/kernel/commons/StringCleanup.java b/src/main/java/sirius/kernel/commons/StringCleanup.java index b2a81be5..02a07c90 100644 --- a/src/main/java/sirius/kernel/commons/StringCleanup.java +++ b/src/main/java/sirius/kernel/commons/StringCleanup.java @@ -33,7 +33,6 @@ public class StringCleanup { private static final Pattern PATTERN_CONTROL_CHARACTERS = Pattern.compile("\\p{Cntrl}"); private static final Pattern PATTERN_WHITESPACE = Pattern.compile("[\\s\\p{Z}]"); private static final Pattern PATTERN_WHITESPACES = Pattern.compile(PATTERN_WHITESPACE + "+"); - private static final Pattern PATTERN_LINEBREAKS = Pattern.compile("\\r?\\n"); private static final Pattern PATTERN_PUNCTUATION = Pattern.compile("\\p{Punct}"); private static final Pattern PATTERN_NON_ALPHA_NUMERIC = Pattern.compile("([^\\p{L}\\d])"); private static final Pattern PATTERN_NON_LETTER = Pattern.compile("\\P{L}"); From 5a0de83718f2d3dbff421143217c626810b556ef Mon Sep 17 00:00:00 2001 From: Christian Schierle Date: Wed, 19 Jul 2023 15:47:22 +0200 Subject: [PATCH 012/111] Adds javadoc to public constants. These are re-used by replacement code in products. Fixes: OX-10173 --- .../sirius/kernel/commons/StringCleanup.java | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/main/java/sirius/kernel/commons/StringCleanup.java b/src/main/java/sirius/kernel/commons/StringCleanup.java index 02a07c90..db272d93 100644 --- a/src/main/java/sirius/kernel/commons/StringCleanup.java +++ b/src/main/java/sirius/kernel/commons/StringCleanup.java @@ -45,33 +45,114 @@ public class StringCleanup { private static final Pattern PATTERN_PP_TAG = Pattern.compile("<(/p|/P)>\\r?\\n?\\t?<([pP])>"); private static final Pattern PATTERN_P_TAG = Pattern.compile("<(/?p|/?P)>"); + /** + * Holds the name of the {@code

} tag. + */ public static final String TAG_P = "p"; + /** + * Holds the name of the {@code
} tag. + */ public static final String TAG_BR = "br"; + /** + * Holds the name of the {@code

} tag. + */ public static final String TAG_DIV = "div"; + /** + * Holds the name of the {@code } tag. + */ public static final String TAG_SPAN = "span"; + /** + * Holds the name of the {@code } tag. + */ public static final String TAG_SMALL = "small"; + /** + * Holds the name of the {@code

} tag. + */ public static final String TAG_H1 = "h1"; + /** + * Holds the name of the {@code

} tag. + */ public static final String TAG_H2 = "h2"; + /** + * Holds the name of the {@code

} tag. + */ public static final String TAG_H3 = "h3"; + /** + * Holds the name of the {@code

} tag. + */ public static final String TAG_H4 = "h4"; + /** + * Holds the name of the {@code

} tag. + */ public static final String TAG_H5 = "h5"; + /** + * Holds the name of the {@code
} tag. + */ public static final String TAG_H6 = "h6"; + /** + * Holds the name of the {@code } tag. + */ public static final String TAG_B = "b"; + /** + * Holds the name of the {@code } tag. + */ public static final String TAG_STRONG = "strong"; + /** + * Holds the name of the {@code } tag. + */ public static final String TAG_I = "i"; + /** + * Holds the name of the {@code } tag. + */ public static final String TAG_EM = "em"; + /** + * Holds the name of the {@code } tag. + */ public static final String TAG_U = "u"; + /** + * Holds the name of the {@code } tag. + */ public static final String TAG_SUP = "sup"; + /** + * Holds the name of the {@code } tag. + */ public static final String TAG_SUB = "sub"; + /** + * Holds the name of the {@code } tag. + */ public static final String TAG_MARK = "mark"; + /** + * Holds the name of the {@code
} tag. + */ public static final String TAG_HR = "hr"; + /** + * Holds the name of the {@code
} tag. + */ public static final String TAG_DL = "dl"; + /** + * Holds the name of the {@code
} tag. + */ public static final String TAG_DT = "dt"; + /** + * Holds the name of the {@code
} tag. + */ public static final String TAG_DD = "dd"; + /** + * Holds the name of the {@code
    } tag. + */ public static final String TAG_OL = "ol"; + /** + * Holds the name of the {@code
      } tag. + */ public static final String TAG_UL = "ul"; + /** + * Holds the name of the {@code
    • } tag. + */ public static final String TAG_LI = "li"; + /** + * Holds a list of all allowed HTML tag names. + */ public static final List ALLOWED_HTML_TAG_NAMES = List.of(TAG_P, TAG_BR, TAG_DIV, From cdae8044daf33576f1bd4a178b869262de0ba187 Mon Sep 17 00:00:00 2001 From: Christian Schierle Date: Wed, 19 Jul 2023 15:48:05 +0200 Subject: [PATCH 013/111] Makes the check for allowed tags case-insensitive Fixes: OX-10173 --- src/main/java/sirius/kernel/commons/Strings.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/sirius/kernel/commons/Strings.java b/src/main/java/sirius/kernel/commons/Strings.java index c5569690..b15b0d04 100644 --- a/src/main/java/sirius/kernel/commons/Strings.java +++ b/src/main/java/sirius/kernel/commons/Strings.java @@ -592,7 +592,8 @@ public static boolean probablyContainsXml(@Nullable String content) { } protected static final Pattern DETECT_ALLOWED_HTML_REGEX = - Pattern.compile("]*>"); + Pattern.compile("]*>", + Pattern.CASE_INSENSITIVE); /** * Determines if the given content contains HTML tags that are {@linkplain #DETECT_ALLOWED_HTML_REGEX allowed} in From 03553c803063d8d060d61017efc8d5de1d9a89d7 Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Wed, 19 Jul 2023 16:52:31 +0200 Subject: [PATCH 014/111] Reuses regex strings instead of relying on Pattern toString Fixes: OX-10173 --- .../sirius/kernel/commons/StringCleanup.java | 22 +++++++++---------- .../java/sirius/kernel/commons/Strings.java | 5 +++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/main/java/sirius/kernel/commons/StringCleanup.java b/src/main/java/sirius/kernel/commons/StringCleanup.java index db272d93..89648dab 100644 --- a/src/main/java/sirius/kernel/commons/StringCleanup.java +++ b/src/main/java/sirius/kernel/commons/StringCleanup.java @@ -31,8 +31,9 @@ public class StringCleanup { private static final Pattern PATTERN_CONTROL_CHARACTERS = Pattern.compile("\\p{Cntrl}"); - private static final Pattern PATTERN_WHITESPACE = Pattern.compile("[\\s\\p{Z}]"); - private static final Pattern PATTERN_WHITESPACES = Pattern.compile(PATTERN_WHITESPACE + "+"); + private static final String REGEX_WHITESPACE = "[\\s\\p{Z}]"; + private static final Pattern PATTERN_WHITESPACE = Pattern.compile(REGEX_WHITESPACE); + private static final Pattern PATTERN_WHITESPACES = Pattern.compile(REGEX_WHITESPACE + "+"); private static final Pattern PATTERN_PUNCTUATION = Pattern.compile("\\p{Punct}"); private static final Pattern PATTERN_NON_ALPHA_NUMERIC = Pattern.compile("([^\\p{L}\\d])"); private static final Pattern PATTERN_NON_LETTER = Pattern.compile("\\P{L}"); @@ -180,7 +181,7 @@ public class StringCleanup { TAG_UL, TAG_LI); - private static final Pattern STRIP_XML_REGEX = Pattern.compile("\\s*" + Strings.DETECT_XML_REGEX + "\\s*"); + private static final Pattern PATTERN_STRIP_XML = Pattern.compile("\\s*" + Strings.REGEX_DETECT_XML + "\\s*"); private static final Map unicodeMapping = new TreeMap<>(); static { @@ -462,14 +463,14 @@ public static String uppercase(@Nonnull String input) { } /** - * Removes all {@linkplain #STRIP_XML_REGEX XML tags} from the given string. + * Removes all {@linkplain #PATTERN_STRIP_XML XML tags} from the given string. * * @param input the input to process * @return the resulting string */ @Nonnull public static String removeXml(@Nonnull String input) { - return STRIP_XML_REGEX.matcher(input).replaceAll(""); + return PATTERN_STRIP_XML.matcher(input).replaceAll(""); } /** @@ -480,10 +481,9 @@ public static String removeXml(@Nonnull String input) { */ @Nonnull public static String removeUnsafeHtml(@Nonnull String input) { - return STRIP_XML_REGEX.matcher(input) - .replaceAll(match -> Strings.DETECT_ALLOWED_HTML_REGEX.matcher(match.group()).matches() ? - match.group() : - ""); + return PATTERN_STRIP_XML.matcher(input) + .replaceAll(match -> Strings.DETECT_ALLOWED_HTML_REGEX.matcher(match.group()) + .matches() ? match.group() : ""); } /** @@ -547,7 +547,7 @@ public static String decodeHtmlEntities(@Nonnull String input) { public static String htmlToPlainText(@Nonnull String input) { String normalizedText = input; - if (STRIP_XML_REGEX.matcher(normalizedText).find()) { + if (PATTERN_STRIP_XML.matcher(normalizedText).find()) { // Reduce all contained whitespaces, tabs, and line breaks normalizedText = Strings.cleanup(normalizedText, StringCleanup::reduceWhitespace); // Replace br tags with line breaks @@ -615,7 +615,7 @@ public static String replaceXml(@Nullable String input) { String contentToStrip; do { contentToStrip = alreadyStrippedContent; - alreadyStrippedContent = STRIP_XML_REGEX.matcher(contentToStrip).replaceFirst(" "); + alreadyStrippedContent = PATTERN_STRIP_XML.matcher(contentToStrip).replaceFirst(" "); } while (!Strings.areEqual(contentToStrip, alreadyStrippedContent)); return alreadyStrippedContent; diff --git a/src/main/java/sirius/kernel/commons/Strings.java b/src/main/java/sirius/kernel/commons/Strings.java index b15b0d04..2c9abebd 100644 --- a/src/main/java/sirius/kernel/commons/Strings.java +++ b/src/main/java/sirius/kernel/commons/Strings.java @@ -575,7 +575,8 @@ public static String cleanup(@Nullable String inputString, @Nonnull Iterable]*>"); + protected static final String REGEX_DETECT_XML = "]*>"; + private static final Pattern PATTERN_DETECT_XML = Pattern.compile(REGEX_DETECT_XML); /** * Determines if the given content contains XML tags. @@ -588,7 +589,7 @@ public static boolean probablyContainsXml(@Nullable String content) { return false; } - return DETECT_XML_REGEX.matcher(content).find(); + return PATTERN_DETECT_XML.matcher(content).find(); } protected static final Pattern DETECT_ALLOWED_HTML_REGEX = From e871a99e6d6bfe3704ff803ffefdbd5929c9b3ec Mon Sep 17 00:00:00 2001 From: Christian Schierle Date: Wed, 26 Jul 2023 15:01:19 +0200 Subject: [PATCH 015/111] Removes unused method again Fixes: OX-10173 --- .../java/sirius/kernel/commons/StringCleanup.java | 13 ------------- .../java/sirius/kernel/commons/StringsTest.java | 2 -- 2 files changed, 15 deletions(-) diff --git a/src/main/java/sirius/kernel/commons/StringCleanup.java b/src/main/java/sirius/kernel/commons/StringCleanup.java index 89648dab..b7d33f50 100644 --- a/src/main/java/sirius/kernel/commons/StringCleanup.java +++ b/src/main/java/sirius/kernel/commons/StringCleanup.java @@ -473,19 +473,6 @@ public static String removeXml(@Nonnull String input) { return PATTERN_STRIP_XML.matcher(input).replaceAll(""); } - /** - * Removes all unsafe HTML tags from the given string. - * - * @param input the input to process - * @return the resulting string - */ - @Nonnull - public static String removeUnsafeHtml(@Nonnull String input) { - return PATTERN_STRIP_XML.matcher(input) - .replaceAll(match -> Strings.DETECT_ALLOWED_HTML_REGEX.matcher(match.group()) - .matches() ? match.group() : ""); - } - /** * Resolves encoded HTML entities to their plain text equivalent in the given string. * diff --git a/src/test/java/sirius/kernel/commons/StringsTest.java b/src/test/java/sirius/kernel/commons/StringsTest.java index 394e3018..e88dfa9c 100644 --- a/src/test/java/sirius/kernel/commons/StringsTest.java +++ b/src/test/java/sirius/kernel/commons/StringsTest.java @@ -176,8 +176,6 @@ void cleanup() { assertEquals("Hello
      World", Strings.cleanup("Hello\nWorld", StringCleanup::nlToBr)); assertEquals("Testalert('Hello World!')", Strings.cleanup("Test", StringCleanup::removeXml)); - assertEquals("
      Test
      ", - Strings.cleanup("
      ", StringCleanup::removeUnsafeHtml)); assertEquals(" äöüÄÖÜß<>\"'&* * * * * * ", Strings.cleanup( " äöüÄÖÜß<>"'&••‣‣⁃⁃", From 623f447455a358279eea848c8bc384844f3afc8b Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Fri, 28 Jul 2023 16:18:26 +0200 Subject: [PATCH 016/111] Migrates some tests to jUnit5 Fixes: OX-10305 --- .../kernel/async/CombinedFutureTest.java | 53 ---- .../sirius/kernel/commons/BOMReaderTest.java | 67 ---- .../sirius/kernel/commons/HasherTest.java | 40 --- .../sirius/kernel/commons/MultiMapTest.java | 29 -- .../kernel/commons/PriorityCollectorTest.java | 20 -- .../sirius/kernel/commons/StreamsTest.java | 46 --- .../sirius/kernel/commons/StringsTest.java | 222 ------------- .../java/sirius/kernel/commons/TrieTest.java | 62 ---- .../sirius/kernel/commons/ValuesTest.java | 37 --- .../sirius/kernel/health/ExceptionsTest.java | 50 --- .../java/sirius/kernel/health/LogHelper.java | 72 ----- .../sirius/kernel/nls/FormatterSpec.groovy | 116 ------- .../kernel/settings/ConfigBuilderSpec.groovy | 67 ---- .../kernel/settings/SettingsSpec.groovy | 43 --- .../sirius/kernel/settings/SettingsTest.kt | 43 --- .../xml/ContentDispositionParserSpec.groovy | 33 -- .../java/sirius/kernel/xml/XMLReaderTest.java | 94 ------ .../sirius/kernel/async/CombinedFutureTest.kt | 52 +++ .../kotlin/sirius/kernel/async/PromiseTest.kt | 4 +- .../sirius/kernel/commons/BOMReaderTest.kt | 74 +++++ .../kotlin/sirius/kernel/commons/FilesTest.kt | 3 + .../sirius/kernel/commons/HasherTest.kt | 47 +++ .../kotlin/sirius/kernel/commons/JsonTest.kt | 4 +- .../sirius/kernel/commons/MultiMapTest.kt | 40 +++ .../kernel/commons/PriorityCollectorTest.kt | 32 ++ .../sirius/kernel/commons/StreamsTest.kt | 41 +++ .../sirius/kernel/commons/StringsTest.kt | 297 ++++++++++++++++++ .../kotlin/sirius/kernel/commons/TrieTest.kt | 68 ++++ .../sirius/kernel/commons/ValuesTest.kt | 38 +++ .../sirius/kernel/health/ExceptionsTest.kt | 51 +++ .../kotlin/sirius/kernel/health/LogHelper.kt | 64 ++++ .../kotlin/sirius/kernel/nls/FormatterTest.kt | 121 +++++++ .../kernel/settings/ConfigBuilderTest.kt | 76 +++++ .../sirius/kernel/settings/SettingsTest.kt | 70 +++++ .../sirius/kernel/testutil/Reflections.kt | 3 +- .../xml/ContentDispositionParserTest.kt | 38 +++ .../kotlin/sirius/kernel/xml/XmlReaderTest.kt | 141 +++++++++ 37 files changed, 1261 insertions(+), 1097 deletions(-) delete mode 100644 src/test/java/sirius/kernel/async/CombinedFutureTest.java delete mode 100644 src/test/java/sirius/kernel/commons/BOMReaderTest.java delete mode 100644 src/test/java/sirius/kernel/commons/HasherTest.java delete mode 100644 src/test/java/sirius/kernel/commons/MultiMapTest.java delete mode 100644 src/test/java/sirius/kernel/commons/PriorityCollectorTest.java delete mode 100644 src/test/java/sirius/kernel/commons/StreamsTest.java delete mode 100644 src/test/java/sirius/kernel/commons/StringsTest.java delete mode 100644 src/test/java/sirius/kernel/commons/TrieTest.java delete mode 100644 src/test/java/sirius/kernel/commons/ValuesTest.java delete mode 100644 src/test/java/sirius/kernel/health/ExceptionsTest.java delete mode 100644 src/test/java/sirius/kernel/health/LogHelper.java delete mode 100644 src/test/java/sirius/kernel/nls/FormatterSpec.groovy delete mode 100644 src/test/java/sirius/kernel/settings/ConfigBuilderSpec.groovy delete mode 100644 src/test/java/sirius/kernel/settings/SettingsSpec.groovy delete mode 100644 src/test/java/sirius/kernel/settings/SettingsTest.kt delete mode 100644 src/test/java/sirius/kernel/xml/ContentDispositionParserSpec.groovy delete mode 100644 src/test/java/sirius/kernel/xml/XMLReaderTest.java create mode 100644 src/test/kotlin/sirius/kernel/async/CombinedFutureTest.kt create mode 100644 src/test/kotlin/sirius/kernel/commons/BOMReaderTest.kt create mode 100644 src/test/kotlin/sirius/kernel/commons/HasherTest.kt create mode 100644 src/test/kotlin/sirius/kernel/commons/MultiMapTest.kt create mode 100644 src/test/kotlin/sirius/kernel/commons/PriorityCollectorTest.kt create mode 100644 src/test/kotlin/sirius/kernel/commons/StreamsTest.kt create mode 100644 src/test/kotlin/sirius/kernel/commons/StringsTest.kt create mode 100644 src/test/kotlin/sirius/kernel/commons/TrieTest.kt create mode 100644 src/test/kotlin/sirius/kernel/commons/ValuesTest.kt create mode 100644 src/test/kotlin/sirius/kernel/health/ExceptionsTest.kt create mode 100644 src/test/kotlin/sirius/kernel/health/LogHelper.kt create mode 100644 src/test/kotlin/sirius/kernel/nls/FormatterTest.kt create mode 100644 src/test/kotlin/sirius/kernel/settings/ConfigBuilderTest.kt create mode 100644 src/test/kotlin/sirius/kernel/settings/SettingsTest.kt create mode 100644 src/test/kotlin/sirius/kernel/xml/ContentDispositionParserTest.kt create mode 100644 src/test/kotlin/sirius/kernel/xml/XmlReaderTest.kt diff --git a/src/test/java/sirius/kernel/async/CombinedFutureTest.java b/src/test/java/sirius/kernel/async/CombinedFutureTest.java deleted file mode 100644 index adb7914d..00000000 --- a/src/test/java/sirius/kernel/async/CombinedFutureTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.async; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * Provides tests for {@link CombinedFuture} - */ -class CombinedFutureTest { - - @Test - void emptyCombinedFutureDontBlock() { - CombinedFuture combinedFuture = new CombinedFuture(); - assertTrue(combinedFuture.asFuture().isCompleted()); - } - - @Test - void combinedFutureActuallyAwait() { - CombinedFuture combinedFuture = new CombinedFuture(); - Future future = new Future(); - combinedFuture.add(future); - assertFalse(combinedFuture.asFuture().isCompleted()); - future.success(); - assertTrue(combinedFuture.asFuture().isCompleted()); - } - - @Test - void combinedFutureWorkWithCompletedFutures() { - CombinedFuture combinedFuture = new CombinedFuture(); - Future future = new Future().success(); - combinedFuture.add(future); - assertTrue(combinedFuture.asFuture().isCompleted()); - } - - @Test - void combinedFutureWorkWithFailingFutures() { - CombinedFuture combinedFuture = new CombinedFuture(); - Future future = new Future(); - combinedFuture.add(future); - future.fail(new IllegalStateException("ThisIsExpected")); - assertTrue(combinedFuture.asFuture().isCompleted()); - } -} diff --git a/src/test/java/sirius/kernel/commons/BOMReaderTest.java b/src/test/java/sirius/kernel/commons/BOMReaderTest.java deleted file mode 100644 index 5c088299..00000000 --- a/src/test/java/sirius/kernel/commons/BOMReaderTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.commons; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.nio.charset.StandardCharsets; - -class BOMReaderTest { - - private static final byte[] WITH_UTF8_BOM = {(byte) 239, (byte) 187, (byte) 191, 'H', 'E', 'L', 'L', 'O'}; - - @Test - void readBOM() throws IOException { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - Writer writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8); - writer.write(Streams.UNICODE_BOM_CHARACTER); - writer.write("HELLO"); - writer.flush(); - BOMReader in = new BOMReader(new InputStreamReader(new ByteArrayInputStream(outputStream.toByteArray()))); - Assertions.assertEquals('H', in.read()); - Assertions.assertEquals('E', in.read()); - } - - @Test - void readWithoutBOM() throws IOException { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - Writer writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8); - writer.write("HELLO"); - writer.flush(); - BOMReader in = new BOMReader(new InputStreamReader(new ByteArrayInputStream(outputStream.toByteArray()))); - Assertions.assertEquals('H', in.read()); - Assertions.assertEquals('E', in.read()); - } - - @ParameterizedTest - @ValueSource(ints = {1, 2, 5}) - void readArrayBOM(int length) throws IOException { - BOMReader in = new BOMReader(new InputStreamReader(new ByteArrayInputStream(WITH_UTF8_BOM))); - char[] buf = new char[length]; - Assertions.assertEquals(length, in.read(buf)); - Assertions.assertEquals('H', buf[0]); - } - - @Test - void readArrayWithoutBOM() throws IOException { - BOMReader in = new BOMReader(new InputStreamReader(new ByteArrayInputStream(WITH_UTF8_BOM))); - char[] buf = new char[2]; - Assertions.assertEquals(2, in.read(buf)); - Assertions.assertEquals('H', buf[0]); - } -} diff --git a/src/test/java/sirius/kernel/commons/HasherTest.java b/src/test/java/sirius/kernel/commons/HasherTest.java deleted file mode 100644 index 1bc554d7..00000000 --- a/src/test/java/sirius/kernel/commons/HasherTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.commons; - -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.io.IOException; -import java.net.URISyntaxException; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class HasherTest { - - @Test - void testMD5() throws URISyntaxException, IOException { - assertEquals("b10a8db164e0754105b7a99be72e3fe5", Hasher.md5().hash("Hello World").toHexString()); - assertEquals("sQqNsWTgdUEFt6mb5y4/5Q==", Hasher.md5().hash("Hello World").toBase64String()); - assertEquals("e59ff97941044f85df5297e1c302d260", - Hasher.md5() - .hashFile(new File(getClass().getResource("/hash_test_file.txt").toURI())) - .toHexString()); - } - - @Test - void testSHA() { - assertEquals("0a4d55a8d778e5022fab701977c5d840bbc486d0", Hasher.sha1().hash("Hello World").toHexString()); - assertEquals("a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e", - Hasher.sha256().hash("Hello World").toHexString()); - assertEquals( - "2c74fd17edafd80e8447b0d46741ee243b7eb74dd2149a0ab1b9246fb30382f27e853d8585719e0e67cbda0daa8f51671064615d645ae27acb15bfb1447f459b", - Hasher.sha512().hash("Hello World").toHexString()); - } -} diff --git a/src/test/java/sirius/kernel/commons/MultiMapTest.java b/src/test/java/sirius/kernel/commons/MultiMapTest.java deleted file mode 100644 index 918fb803..00000000 --- a/src/test/java/sirius/kernel/commons/MultiMapTest.java +++ /dev/null @@ -1,29 +0,0 @@ -package sirius.kernel.commons; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; - -class MultiMapTest { - @Test - void test() { - MultiMap mm = MultiMap.create(); - mm.put("A", "A"); - mm.put("A", "B"); - assertArrayEquals(new String[]{"A", "B"}, mm.get("A").toArray(new String[mm.get("A").size()])); - mm.put("A", "B"); - assertArrayEquals(new String[]{"A", "B", "B"}, mm.get("A").toArray(new String[mm.get("A").size()])); - mm.remove("A", "B"); - assertArrayEquals(new String[]{"A"}, mm.get("A").toArray(new String[mm.get("A").size()])); - mm.put("B", "A"); - mm.put("B", "C"); - assertArrayEquals(new String[]{"A", "B"}, mm.keySet().toArray(new String[mm.keySet().size()])); - assertArrayEquals(new String[]{"A", "A", "C"}, mm.values().toArray(new String[mm.values().size()])); - mm.getUnderlyingMap().clear(); - assertArrayEquals(new String[0], mm.get("A").toArray(new String[mm.get("A").size()])); - mm.put("B", "C"); - assertArrayEquals(new String[]{"C"}, mm.get("B").toArray(new String[mm.get("B").size()])); - mm.clear(); - assertArrayEquals(new String[0], mm.get("B").toArray(new String[mm.get("B").size()])); - } -} diff --git a/src/test/java/sirius/kernel/commons/PriorityCollectorTest.java b/src/test/java/sirius/kernel/commons/PriorityCollectorTest.java deleted file mode 100644 index 5236bcad..00000000 --- a/src/test/java/sirius/kernel/commons/PriorityCollectorTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package sirius.kernel.commons; - -import org.junit.jupiter.api.Test; - -import java.util.Arrays; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class PriorityCollectorTest { - @Test - void test() { - PriorityCollector c = PriorityCollector.create(); - c.addDefault("B"); - c.add(50, "A"); - c.add(101, "C"); - assertEquals(Arrays.asList("A", "B", "C"), c.getData()); - c.getData().clear(); - assertEquals(3, c.size()); - } -} diff --git a/src/test/java/sirius/kernel/commons/StreamsTest.java b/src/test/java/sirius/kernel/commons/StreamsTest.java deleted file mode 100644 index aee503cb..00000000 --- a/src/test/java/sirius/kernel/commons/StreamsTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.commons; - -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class StreamsTest { - - @Test - void transferTest() throws IOException { - String testString = "Hello from the other side..."; - - ByteArrayInputStream in = new ByteArrayInputStream(testString.getBytes(StandardCharsets.UTF_8)); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Streams.transfer(in, out); - - assertEquals(testString, new String(out.toByteArray(), StandardCharsets.UTF_8)); - } - - @Test - void largeTransferTest() throws IOException { - StringBuilder builder = new StringBuilder(); - builder.append("Hello World".repeat(10_000)); - - String testString = builder.toString(); - - ByteArrayInputStream in = new ByteArrayInputStream(testString.getBytes(StandardCharsets.UTF_8)); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Streams.transfer(in, out); - - assertEquals(testString, new String(out.toByteArray(), StandardCharsets.UTF_8)); - } -} diff --git a/src/test/java/sirius/kernel/commons/StringsTest.java b/src/test/java/sirius/kernel/commons/StringsTest.java deleted file mode 100644 index e88dfa9c..00000000 --- a/src/test/java/sirius/kernel/commons/StringsTest.java +++ /dev/null @@ -1,222 +0,0 @@ -package sirius.kernel.commons; - -import org.junit.jupiter.api.Test; - -import java.util.List; -import java.util.regex.Pattern; - -import static org.junit.jupiter.api.Assertions.*; - -class StringsTest { - - @Test - void isFilled() { - assertTrue(Strings.isFilled("Test")); - assertTrue(Strings.isFilled(" ")); - assertFalse(Strings.isFilled(null)); - assertFalse(Strings.isFilled("")); - } - - @Test - void isEmpty() { - assertFalse(Strings.isEmpty("Test")); - assertFalse(Strings.isEmpty(" ")); - assertTrue(Strings.isEmpty(null)); - assertTrue(Strings.isEmpty("")); - } - - @Test - void equalIgnoreCase() { - assertTrue(Strings.equalIgnoreCase("A", "a")); - assertFalse(Strings.equalIgnoreCase("A", "b")); - assertTrue(Strings.equalIgnoreCase("", null)); - assertFalse(Strings.equalIgnoreCase(" ", null)); - assertTrue(Strings.equalIgnoreCase(null, null)); - } - - @Test - void areEqual() { - assertTrue(Strings.areEqual("A", "A")); - assertFalse(Strings.areEqual("a", "A")); - assertFalse(Strings.areEqual("a", "A")); - assertTrue(Strings.areEqual("a", "A", x -> x.toString().toLowerCase())); - assertTrue(Strings.areEqual("", null)); - assertFalse(Strings.areEqual(" ", null)); - assertTrue(Strings.areEqual(null, null)); - } - - @Test - void areTrimmedEqual() { - assertTrue(Strings.areTrimmedEqual("A ", "A")); - assertFalse(Strings.areTrimmedEqual("a", "A ")); - assertTrue(Strings.areTrimmedEqual("", null)); - assertTrue(Strings.areTrimmedEqual(" ", null)); - assertTrue(Strings.areTrimmedEqual(null, null)); - } - - @Test - void toStringMethod() { - assertEquals("A", Strings.toString("A")); - assertEquals("", Strings.toString("")); - assertNull(Strings.toString(null)); - } - - @Test - void apply() { - assertEquals("B A", Strings.apply("%s A", "B")); - assertEquals("A null", Strings.apply("A %s", (String) null)); - } - - @Test - void firstFilled() { - assertEquals("A", Strings.firstFilled("A")); - assertEquals("A", Strings.firstFilled("A", "B")); - assertEquals("A", Strings.firstFilled(null, "A")); - assertEquals("A", Strings.firstFilled("", "A")); - assertEquals("A", Strings.firstFilled(null, null, "A")); - assertNull(Strings.firstFilled()); - assertNull(Strings.firstFilled((String) null)); - assertNull(Strings.firstFilled("")); - } - - @Test - void urlEncode() { - assertEquals("A%3FTEST%26B%C3%84%C3%96%C3%9C", Strings.urlEncode("A?TEST&BÄÖÜ")); - } - - @Test - void urlDecode() { - assertEquals("A?TEST&BÄÖÜ", Strings.urlDecode("A%3FTEST%26B%C3%84%C3%96%C3%9C")); - } - - @Test - void split() { - assertEquals(Tuple.create("A", "B"), Strings.split("A|B", "|")); - assertEquals(Tuple.create("A", "&B"), Strings.split("A&&B", "&")); - assertEquals(Tuple.create("A", "B"), Strings.split("A&&B", "&&")); - assertEquals(Tuple.create("A", ""), Strings.split("A|", "|")); - assertEquals(Tuple.create("", "B"), Strings.split("|B", "|")); - assertEquals(Tuple.create("A&B", null), Strings.split("A&B", "|")); - } - - @Test - void splitSmart() { - assertEquals(List.of("a"), Strings.splitSmart("a", 2)); - assertEquals(List.of(), Strings.splitSmart(null, 0)); - assertEquals(List.of(), Strings.splitSmart(null, 2)); - assertEquals(List.of(), Strings.splitSmart("", 0)); - assertEquals(List.of(), Strings.splitSmart("", 2)); - assertEquals(List.of("das ist", "ein", "Test"), Strings.splitSmart("das ist ein Test", 7)); - assertEquals(List.of("lange-w", "örter-w", "erden-a", "uch-get", "rennt"), - Strings.splitSmart("lange-wörter-werden-auch-getrennt", 7)); - assertEquals(List.of("Ein langer Text kann in eine Zeile"), - Strings.splitSmart("Ein langer Text kann in eine Zeile", 40)); - } - - @Test - void join() { - assertEquals("A,B,C", Strings.join(",", "A", "B", "C")); - assertEquals("A,C", Strings.join(",", "A", null, "", "C")); - assertEquals("A", Strings.join(",", "A")); - assertEquals("", Strings.join(",")); - assertEquals("ABC", Strings.join("", "A", "B", "C")); - } - - @Test - void replaceAll() { - assertEquals("A<B&C&&D&;&E", - Strings.replaceAll(Pattern.compile("&([a-zA-Z0-9]{0,6};?)"), - "A<B&C&&D&;&E", - s -> (s.endsWith(";") && !s.startsWith(";") ? "&" : "&") + s)); - } - - @Test - void leftPad() { - assertEquals(" A", Strings.leftPad("A", " ", 4)); - assertEquals(" A", Strings.leftPad("A", " ", 5)); - assertEquals(" A", Strings.leftPad("A", " ", 4)); - assertEquals("AAA", Strings.leftPad("AAA", " ", 2)); - } - - @Test - void rightPad() { - assertEquals("A ", Strings.rightPad("A", " ", 4)); - assertEquals("A ", Strings.rightPad("A", " ", 5)); - assertEquals("A ", Strings.rightPad("A", " ", 4)); - assertEquals("AAA", Strings.rightPad("AAA", " ", 2)); - } - - @Test - void reduceCharacters() { - assertEquals("Hello", StringCleanup.reduceCharacters("Hello")); - assertSame("Hello", StringCleanup.reduceCharacters("Hello")); - assertEquals("Hello", StringCleanup.reduceCharacters("Héllo")); - assertEquals("AOEO", StringCleanup.reduceCharacters("AÖO")); - assertEquals("AEAAE", StringCleanup.reduceCharacters("ÄAÄ")); - } - - @Test - void cleanup() { - assertEquals("Hel lo", Strings.cleanup("Hel lo ", StringCleanup::trim)); - assertEquals("Hel lo ", Strings.cleanup("Hel \t \t \r\n lo ", StringCleanup::reduceWhitespace)); - assertEquals("Hello", Strings.cleanup("Hel \t \t \n lo ", StringCleanup::removeWhitespace)); - assertEquals("Hello", Strings.cleanup("Héllo", StringCleanup::reduceCharacters)); - assertEquals("hello", Strings.cleanup("Héllo", StringCleanup::reduceCharacters, StringCleanup::lowercase)); - assertEquals("HELLO", Strings.cleanup("Héllo", StringCleanup::reduceCharacters, StringCleanup::uppercase)); - assertEquals("Hello", Strings.cleanup("Hel-lo", StringCleanup::removePunctuation)); - assertEquals("Hello", Strings.cleanup("\10Hello", StringCleanup::removeControlCharacters)); - assertEquals("Test", Strings.cleanup("Test", StringCleanup::replaceXml, StringCleanup::trim)); - assertEquals("Test", Strings.cleanup("Test
      ", StringCleanup::replaceXml, StringCleanup::trim)); - assertEquals("Test Blubb", - Strings.cleanup("Test
      Blubb
      ", StringCleanup::replaceXml, StringCleanup::trim)); - assertEquals("foo having < 3 m, with >= 3 m", - Strings.cleanup("foo having < 3 m, with >= 3 m", StringCleanup::replaceXml, StringCleanup::trim)); - assertEquals("<b>Foo <br /> Bar</b>", - Strings.cleanup("Foo
      Bar
      ", StringCleanup::escapeXml)); - assertEquals("Hello
      World", Strings.cleanup("Hello\nWorld", StringCleanup::nlToBr)); - assertEquals("Testalert('Hello World!')", - Strings.cleanup("Test", StringCleanup::removeXml)); - assertEquals(" äöüÄÖÜß<>\"'&* * * * * * ", - Strings.cleanup( - " äöüÄÖÜß<>"'&••‣‣⁃⁃", - StringCleanup::decodeHtmlEntities)); - } - - @Test - void probablyContainsXml() { - assertTrue(Strings.probablyContainsXml("Test")); - assertTrue(Strings.probablyContainsXml("
      ")); - assertTrue(Strings.probablyContainsXml("
      ")); - assertTrue(Strings.probablyContainsXml("
      ")); - assertTrue(Strings.probablyContainsXml("
")); - assertTrue(Strings.probablyContainsXml("")); - assertFalse(Strings.probablyContainsXml("foo having < 3 m, with >= 3 m")); - assertFalse(Strings.probablyContainsXml("foo having <3 m, with > 3 m")); - } - - @Test - void containsAllowedHtml() { - assertTrue(Strings.containsAllowedHtml("Test")); - assertTrue(Strings.containsAllowedHtml("
")); - assertTrue(Strings.containsAllowedHtml("
")); - assertTrue(Strings.containsAllowedHtml("
")); - assertTrue(Strings.containsAllowedHtml("")); - assertTrue(Strings.containsAllowedHtml("
")); - assertFalse(Strings.containsAllowedHtml("")); - assertFalse(Strings.containsAllowedHtml("Test ")); - assertFalse(Strings.containsAllowedHtml("foo having < 3 m, with >= 3 m")); - assertFalse(Strings.containsAllowedHtml("foo having <3 m, with > 3 m")); - } - - @Test - void limit() { - assertEquals("", Strings.limit(null, 10, false)); - assertEquals("", Strings.limit(null, 10, true)); - assertEquals("", Strings.limit("", 10, false)); - assertEquals("", Strings.limit("", 10, true)); - assertEquals("ABCDE", Strings.limit("ABCDE", 10, false)); - assertEquals("ABCDE", Strings.limit("ABCDE", 10, true)); - assertEquals("ABCDEFGHIJ", Strings.limit("ABCDEFGHIJKLMNOP", 10, false)); - assertEquals("ABCDEFGHI…", Strings.limit("ABCDEFGHIJKLMNOP", 10, true)); - } -} diff --git a/src/test/java/sirius/kernel/commons/TrieTest.java b/src/test/java/sirius/kernel/commons/TrieTest.java deleted file mode 100644 index 1d0bd777..00000000 --- a/src/test/java/sirius/kernel/commons/TrieTest.java +++ /dev/null @@ -1,62 +0,0 @@ -package sirius.kernel.commons; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; - -import static org.junit.jupiter.api.Assertions.*; - -class TrieTest { - - private static Trie trie; - - @BeforeAll - static void createTrie() { - trie = Trie.create(); - trie.put("one", 1); - trie.put("on", 2); - trie.put("one1", 3); - trie.put("two", 4); - trie.put("three", 5); - trie.put("thrae", 6); - trie.put("th", 7); - } - - @Test - void isFilled() { - String check = "I'd like to have three beer please"; - Trie.ContainmentIterator iter = trie.iterator(); - - int found = 0; - - for (int i = 0; i < check.length(); i++) { - if (!iter.doContinue(check.charAt(i))) { - if (iter.isCompleted()) { - found = iter.getValue(); - } - iter.resetWith(check.charAt(i)); - } - } - - assertEquals(5, found); - - assertEquals(2, (int) trie.get("on")); - assertNull(trie.get("onx")); - assertTrue(trie.containsKey("thrae")); - assertFalse(trie.containsKey("thre")); - } - - @Test - void keySet() { - assertEquals(7, trie.size()); - assertEquals(new HashSet<>(Arrays.asList("one", "on", "one1", "two", "three", "thrae", "th")), trie.keySet()); - assertEquals(new HashSet<>(Arrays.asList("one", "on", "one1", "two", "three", "thrae", "th")), - trie.getAllKeysBeginningWith("")); - assertEquals(new HashSet<>(Arrays.asList("one", "on", "one1")), trie.getAllKeysBeginningWith("on")); - assertEquals(Collections.singleton("three"), trie.getAllKeysBeginningWith("three")); - assertEquals(0, trie.getAllKeysBeginningWith("threee").size()); - } -} diff --git a/src/test/java/sirius/kernel/commons/ValuesTest.java b/src/test/java/sirius/kernel/commons/ValuesTest.java deleted file mode 100644 index ea7bb060..00000000 --- a/src/test/java/sirius/kernel/commons/ValuesTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.commons; - -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; - -class ValuesTest { - @Test - void at() { - assertEquals("A", Values.of(new String[]{"A", "B", "C"}).at(0).asString()); - assertFalse(Values.of(new String[]{"A", "B", "C"}).at(10).isFilled()); - } - - @Test - void excelStyleColumns() { - assertEquals("A", Values.of(new String[]{"A", "B", "C"}).at("A").asString()); - assertEquals("C", Values.of(new String[]{"A", "B", "C"}).at("C").asString()); - List test = new ArrayList<>(); - for (int i = 1; i < 100; i++) { - test.add(String.valueOf(i)); - } - assertEquals("28", Values.of(test).at("AB").asString()); - assertEquals("34", Values.of(test).at("AH").asString()); - } -} diff --git a/src/test/java/sirius/kernel/health/ExceptionsTest.java b/src/test/java/sirius/kernel/health/ExceptionsTest.java deleted file mode 100644 index 5ce3c784..00000000 --- a/src/test/java/sirius/kernel/health/ExceptionsTest.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.health; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import sirius.kernel.SiriusExtension; - -import java.util.logging.Level; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -/** - * Tests for the {@link Exceptions} class. - */ -@ExtendWith(SiriusExtension.class) -class ExceptionsTest { - - @Test - void testDeprecatedMethodCallWarner() { - LogHelper.clearMessages(); - caller(); - assertTrue(LogHelper.hasMessage(Level.WARNING, - Exceptions.DEPRECATION_LOG, - "^The deprecated method 'sirius.kernel.health.ExceptionsTest.deprecatedMethod'" - + " was called by 'sirius.kernel.health.ExceptionsTest.caller'")); - } - - private void caller() { - deprecatedMethod(); - } - - private void deprecatedMethod() { - Exceptions.logDeprecatedMethodUse(); - } - - @Test - void testRootCauseRemains() { - HandledException root = Exceptions.createHandled().withSystemErrorMessage("Root Cause").handle(); - HandledException ex = Exceptions.handle(new Exception(new IllegalArgumentException(root))); - assertEquals(root, Exceptions.getRootCause(ex)); - } -} diff --git a/src/test/java/sirius/kernel/health/LogHelper.java b/src/test/java/sirius/kernel/health/LogHelper.java deleted file mode 100644 index 9b0c16be..00000000 --- a/src/test/java/sirius/kernel/health/LogHelper.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.health; - -import sirius.kernel.di.std.Part; - -import java.util.logging.Level; -import java.util.regex.Pattern; - -/** - * Helps to create tests which expect certain kind of log messages to be created. - */ -public class LogHelper { - - private LogHelper() { - } - - @Part - private static MemoryBasedHealthMonitor monitor; - - /** - * Clears all log message. - *

- * It is probably a good idea to clear all previously logged messages prior to a test. - */ - public static void clearMessages() { - monitor.messages.clear(); - } - - /** - * Determines if a message matching the given criteria was logged. - * - * @param level the expected level - * @param logger the expected target logger - * @param pattern the regular expression to find within the message. Use ^foobar$ to match - * the whole message. - * @return true if a matching message was found, false otherwise - */ - public static boolean hasMessage(Level level, String logger, String pattern) { - Pattern regEx = Pattern.compile(pattern); - for (LogMessage msg : monitor.getMessages()) { - if (msg.getReceiver().getName().equals(logger)) { - if (level == msg.getLogLevel()) { - if (regEx.matcher(msg.getMessage()).find()) { - return true; - } - } - } - } - - return false; - } - - /** - * Determines if a message matching the given criteria was logged. - * - * @param level the expected level - * @param logger the expected target logger - * @param pattern the regular expression to find within the message. Use ^foobar$ to match - * the whole message. - * @return true if a matching message was found, false otherwise - */ - public static boolean hasMessage(Level level, Log logger, String pattern) { - return hasMessage(level, logger.getName(), pattern); - } -} diff --git a/src/test/java/sirius/kernel/nls/FormatterSpec.groovy b/src/test/java/sirius/kernel/nls/FormatterSpec.groovy deleted file mode 100644 index 2404fe3a..00000000 --- a/src/test/java/sirius/kernel/nls/FormatterSpec.groovy +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.nls - -import sirius.kernel.BaseSpecification -import sirius.kernel.commons.Context - -class FormatterSpec extends BaseSpecification { - - def "format replaces parameters"() { - given: - def pattern = "Test \${foo}" - when: - def result = Formatter.create(pattern).set("foo", "bar").format() - then: - result == "Test bar" - } - - def "set trims and calls toUserString on parameters"() { - given: - def pattern = "Test \${foo} \${bar}" - when: - def result = Formatter.create(pattern).set("foo", true).set("bar", " test ").format() - then: - result == "Test Ja test" - } - - def "setDirect neither trims nor calls toUserString on parameters"() { - given: - def pattern = "Test \${foo} \${bar}" - when: - def result = Formatter. - create(pattern). - setDirect(Context.create().set("foo", true)). - setDirect("bar", " test "). - format() - then: - result == "Test true test " - } - - def "smartFormat skips empty block"() { - given: - def pattern = "Test[ \${foo}]" - when: - def result = Formatter.create(pattern).set("foo", null).smartFormat() - then: - result == "Test" - } - - def "smartFormat accepts nested blocks"() { - given: - def pattern = "Test[[ \${foo}] bar \${baz}]" - when: - def result1 = Formatter.create(pattern).set("foo", null).set("baz", null).smartFormat() - def result2 = Formatter.create(pattern).set("foo", "foo").set("baz", null).smartFormat() - def result3 = Formatter.create(pattern).set("foo", null).set("baz", "baz").smartFormat() - def result4 = Formatter.create(pattern).set("foo", "foo").set("baz", "baz").smartFormat() - then: - result1 == "Test" - result2 == "Test foo bar " - result3 == "Test bar baz" - result4 == "Test foo bar baz" - } - - def "format fails when using unknown parameter"() { - given: - def pattern = "Test \${foo}" - when: - Formatter.create(pattern).smartFormat() - then: - thrown(IllegalArgumentException) - } - - def "format fails for missing }"() { - given: - def pattern = "Test \${foo" - when: - Formatter.create(pattern).smartFormat() - then: - thrown(IllegalArgumentException) - } - - def "format fails for missing ]"() { - given: - def pattern = "Test [\${foo}" - when: - Formatter.create(pattern).smartFormat() - then: - thrown(IllegalArgumentException) - } - - def "smartFormat fails for additional ]"() { - given: - def pattern = "Test [\${foo}]]" - when: - Formatter.create(pattern).smartFormat() - then: - thrown(IllegalArgumentException) - } - - def "createJSFormatter works"() { - given: - def pattern = "foo = '\${foo}' bar = \"\${bar}\"" - when: - def result = Formatter.createJSFormatter(pattern).set("foo", "d'or").set("bar", "\"buzz\"").format() - then: - result == "foo = 'd\\'or' bar = \"\\\"buzz\\\"\"" - } - -} diff --git a/src/test/java/sirius/kernel/settings/ConfigBuilderSpec.groovy b/src/test/java/sirius/kernel/settings/ConfigBuilderSpec.groovy deleted file mode 100644 index 4989b9f4..00000000 --- a/src/test/java/sirius/kernel/settings/ConfigBuilderSpec.groovy +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.settings - -import sirius.kernel.BaseSpecification -import sirius.kernel.commons.Strings - -class ConfigBuilderSpec extends BaseSpecification { - - def "config builder works"() { - given: - def configBuilder = new ConfigBuilder() - when: - configBuilder.addVariable("scope.foo", "true"); - then: - Strings.areEqual(configBuilder.toString(), "scope.foo = true") - } - - def "scope unfolding works"() { - given: - def configBuilder = new ConfigBuilder() - when: - configBuilder.addVariable("scope.foo", "true"); - configBuilder.addVariable("scope.bar", "false"); - then: - Strings.areEqual(configBuilder.toString(), "scope {\n" + - " foo = true\n" + - " bar = false\n" + - "}") - } - - def "complex example works"() { - given: - def configBuilder = new ConfigBuilder() - when: - configBuilder.addVariable("scopeA.foo", "true"); - configBuilder.addVariable("scopeA.bar", "true"); - configBuilder.addVariable("scopeB.foo", "\"enabled\""); - configBuilder.addVariable("scopeB.scopeC.foo", "\"disabled\""); - configBuilder.addVariable("scopeB.scopeC.bar", "\"enabled\""); - configBuilder.addVariable("foo", "\"disabled\""); - configBuilder.addVariable("foo.bar.test", "42"); - then: - Strings.areEqual(configBuilder.toString(), "foo = \"disabled\"\n" + - "foo.bar.test = 42\n" + - "\n" + - "scopeA {\n" + - " foo = true\n" + - " bar = true\n" + - "}\n" + - "\n" + - "scopeB {\n" + - " foo = \"enabled\"\n" + - "\n" + - " scopeC {\n" + - " foo = \"disabled\"\n" + - " bar = \"enabled\"\n" + - " }\n" + - "}") - } -} diff --git a/src/test/java/sirius/kernel/settings/SettingsSpec.groovy b/src/test/java/sirius/kernel/settings/SettingsSpec.groovy deleted file mode 100644 index f2113226..00000000 --- a/src/test/java/sirius/kernel/settings/SettingsSpec.groovy +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.settings - - -import sirius.kernel.BaseSpecification -import sirius.kernel.Sirius - -import java.util.stream.Collectors - -class SettingsSpec extends BaseSpecification { - - def "inner configs are delivered in their given order"() { - when: - def keys = Sirius.getSettings().getConfigs("test-configs").stream().map({ c -> c.getString("value") }).collect( - Collectors.toList()) - then: - keys == ["2", "1", "3"] - } - - def "inner configs are delivered as sorted by their priority if given"() { - when: - def keys = Sirius.getSettings().getConfigs("test-configs-sorted").stream().map({ c -> c.getString("value") }).collect( - Collectors.toList()) - then: - keys == ["1", "2", "3"] - } - - def "no exception is thrown for retrieving a non-existent extension, even when settings are strict"() { - when: - def extension = Sirius.getSettings().getExtension("non-existent", "not-specified") - then: - extension == null - and: - noExceptionThrown() - } -} diff --git a/src/test/java/sirius/kernel/settings/SettingsTest.kt b/src/test/java/sirius/kernel/settings/SettingsTest.kt deleted file mode 100644 index fd87d280..00000000 --- a/src/test/java/sirius/kernel/settings/SettingsTest.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.settings - -import com.typesafe.config.ConfigFactory -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test - -class SettingsTest { - - @Test - fun `Settings#getTranslatedString works as expected`() { - val settings = Settings( - ConfigFactory.parseString( - """ - directKey = "test" - intKey = 5 - translatedKey = "${'$'}testKey" - mapKey { - en: test - de: "ein Test" - default: "fallback" - } - """ - ), false - ) - - Assertions.assertEquals("", settings.getTranslatedString("unknown", "en")) - Assertions.assertEquals("test", settings.getTranslatedString("directKey", "en")) - Assertions.assertEquals("testKey", settings.getTranslatedString("translatedKey", "en")) - Assertions.assertEquals("5", settings.getTranslatedString("intKey", "en")) - Assertions.assertEquals("test", settings.getTranslatedString("mapKey", "en")) - Assertions.assertEquals("ein Test", settings.getTranslatedString("mapKey", "de")) - Assertions.assertEquals("fallback", settings.getTranslatedString("mapKey", "xx")) - } - -} diff --git a/src/test/java/sirius/kernel/xml/ContentDispositionParserSpec.groovy b/src/test/java/sirius/kernel/xml/ContentDispositionParserSpec.groovy deleted file mode 100644 index 20390d76..00000000 --- a/src/test/java/sirius/kernel/xml/ContentDispositionParserSpec.groovy +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.xml - - -import spock.lang.Specification - -class ContentDispositionParserSpec extends Specification { - - def "parseContentDisposition regex works for different scenarios"() { - expect: - ContentDispositionParser.parseFileName(input).get() == output - - where: - input | output - "attachment; filename=\"test.pdf\"" | "test.pdf" - "inline; filename=test.pdf" | "test.pdf" - "attachment; filename=test.pdf ; size=\"2000\"" | "test.pdf" - "attachment; size=\"2000\" ; filename=test.pdf" | "test.pdf" - "attachment; filename=\"test pdf doc.pdf\"" | "test pdf doc.pdf" - "inline; filename*=UTF-8''test.pdf" | "test.pdf" - "inline; filename*=\"UTF-8''test.pdf\"" | "test.pdf" - "inline; filename*=\"UTF-8''test%20pdf%20doc.pdf\"" | "test pdf doc.pdf" - "inline; filename*=UTF-8''test%20pdf%20doc.pdf" | "test pdf doc.pdf" - "attachment; filename*=iso-8859-1'en'file%27%20%27name.jpg" | "file' 'name.jpg" - } -} diff --git a/src/test/java/sirius/kernel/xml/XMLReaderTest.java b/src/test/java/sirius/kernel/xml/XMLReaderTest.java deleted file mode 100644 index 0d702eb9..00000000 --- a/src/test/java/sirius/kernel/xml/XMLReaderTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.xml; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import sirius.kernel.commons.ValueHolder; -import sirius.kernel.health.Counter; -import sirius.kernel.SiriusExtension; - -import java.io.ByteArrayInputStream; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; - -@ExtendWith(SiriusExtension.class) -class XMLReaderTest { - - @Test - @DisplayName("XMLReader extracts XPATH expression") - void readXpath() throws Exception { - ValueHolder readString = ValueHolder.of(null); - Counter nodeCount = new Counter(); - XMLReader reader = new XMLReader(); - reader.addHandler("test", node -> { - nodeCount.inc(); - readString.set(node.queryString("value")); - }); - - reader.parse(new ByteArrayInputStream( - "125".getBytes())); - - assertEquals("5", readString.get()); - assertEquals(3, nodeCount.getCount(), "parsed invalid count of nodes"); - } - - @Test - @DisplayName("XMLReader supports compound XPATH paths") - void readXpathCompound() throws Exception { - ValueHolder shouldToggle = ValueHolder.of(false); - ValueHolder shouldNotToggle = ValueHolder.of(false); - XMLReader reader = new XMLReader(); - reader.addHandler("doc/test/value", node -> shouldToggle.set(true)); - reader.addHandler("value", node -> shouldNotToggle.set(true)); - - reader.parse(new ByteArrayInputStream( - "125".getBytes())); - - assertTrue(shouldToggle.get()); - assertFalse(shouldNotToggle.get()); - } - - @Test - @DisplayName("XMLReader reads attributes") - void readXpathAttributes() throws Exception { - Map attributes = new HashMap<>(); - ValueHolder attribute = ValueHolder.of(""); - XMLReader reader = new XMLReader(); - reader.addHandler("test", node -> { - attributes.putAll(node.getAttributes()); - attribute.set(node.getAttribute("namedAttribute").asString()); - }); - - reader.parse(new ByteArrayInputStream("1".getBytes())); - - assertEquals(2, attributes.size()); - assertEquals("abc", attribute.get()); - } - - @Test - @DisplayName("reading non existing attributes does not throw errors") - void readXpathMissingAttributes() throws Exception { - Map attributes = new HashMap<>(); - ValueHolder attribute = ValueHolder.of("wrongValue"); - XMLReader reader = new XMLReader(); - reader.addHandler("test", node -> { - attributes.putAll(node.getAttributes()); - attribute.set(node.getAttribute("namedAttribute").asString()); - }); - - reader.parse(new ByteArrayInputStream("1".getBytes())); - - assertEquals(0, attributes.size()); - assertEquals("", attribute.get()); - } -} diff --git a/src/test/kotlin/sirius/kernel/async/CombinedFutureTest.kt b/src/test/kotlin/sirius/kernel/async/CombinedFutureTest.kt new file mode 100644 index 00000000..69f73a58 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/async/CombinedFutureTest.kt @@ -0,0 +1,52 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.async + +import org.junit.jupiter.api.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +/** + * Tests the [CombinedFuture] class. + */ +class CombinedFutureTest { + + @Test + fun emptyCombinedFutureDontBlock() { + val combinedFuture = CombinedFuture() + assertTrue { combinedFuture.asFuture().isCompleted } + } + + @Test + fun combinedFutureActuallyAwait() { + val combinedFuture = CombinedFuture() + val future = Future() + combinedFuture.add(future) + assertFalse { combinedFuture.asFuture().isCompleted } + future.success() + assertTrue { combinedFuture.asFuture().isCompleted } + } + + @Test + fun combinedFutureWorkWithCompletedFutures() { + val combinedFuture = CombinedFuture() + val future = Future().success() + combinedFuture.add(future) + assertTrue { combinedFuture.asFuture().isCompleted } + } + + @Test + fun combinedFutureWorkWithFailingFutures() { + val combinedFuture = CombinedFuture() + val future = Future() + combinedFuture.add(future) + future.fail(IllegalStateException("ThisIsExpected")) + assertTrue { combinedFuture.asFuture().isCompleted } + } +} diff --git a/src/test/kotlin/sirius/kernel/async/PromiseTest.kt b/src/test/kotlin/sirius/kernel/async/PromiseTest.kt index c2cf941a..ac50ff6f 100644 --- a/src/test/kotlin/sirius/kernel/async/PromiseTest.kt +++ b/src/test/kotlin/sirius/kernel/async/PromiseTest.kt @@ -13,7 +13,9 @@ import sirius.kernel.commons.ValueHolder import kotlin.test.assertEquals import kotlin.test.assertTrue - +/** + * Tests the [Promise] class. + */ class PromiseTest { @Test diff --git a/src/test/kotlin/sirius/kernel/commons/BOMReaderTest.kt b/src/test/kotlin/sirius/kernel/commons/BOMReaderTest.kt new file mode 100644 index 00000000..886eebf3 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/BOMReaderTest.kt @@ -0,0 +1,74 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons + +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import java.io.* +import java.nio.charset.StandardCharsets +import kotlin.test.assertEquals + +/** + * Tests the [BOMReader] class. + */ +class BOMReaderTest { + @Test + fun readBOM() { + val outputStream = ByteArrayOutputStream() + val writer: Writer = OutputStreamWriter(outputStream, StandardCharsets.UTF_8) + writer.write(Streams.UNICODE_BOM_CHARACTER) + writer.write("HELLO") + writer.flush() + val reader = BOMReader(InputStreamReader(ByteArrayInputStream(outputStream.toByteArray()))) + assertEquals('H'.code, reader.read()) + assertEquals('E'.code, reader.read()) + } + + @Test + fun readWithoutBOM() { + val outputStream = ByteArrayOutputStream() + val writer: Writer = OutputStreamWriter(outputStream, StandardCharsets.UTF_8) + writer.write("HELLO") + writer.flush() + val reader = BOMReader(InputStreamReader(ByteArrayInputStream(outputStream.toByteArray()))) + assertEquals('H'.code, reader.read()) + assertEquals('E'.code, reader.read()) + } + + @ParameterizedTest + @ValueSource(ints = [1, 2, 5]) + fun readArrayBOM(length: Int) { + val reader = BOMReader(InputStreamReader(ByteArrayInputStream(WITH_UTF8_BOM))) + val buffer = CharArray(length) + assertEquals(length, reader.read(buffer)) + assertEquals('H', buffer[0]) + } + + @Test + fun readArrayWithoutBOM() { + val reader = BOMReader(InputStreamReader(ByteArrayInputStream(WITH_UTF8_BOM))) + val buffer = CharArray(2) + assertEquals(2, reader.read(buffer)) + assertEquals('H', buffer[0]) + } + + companion object { + private val WITH_UTF8_BOM = byteArrayOf( + 239.toByte(), + 187.toByte(), + 191.toByte(), + 'H'.code.toByte(), + 'E'.code.toByte(), + 'L'.code.toByte(), + 'L'.code.toByte(), + 'O'.code.toByte() + ) + } +} diff --git a/src/test/kotlin/sirius/kernel/commons/FilesTest.kt b/src/test/kotlin/sirius/kernel/commons/FilesTest.kt index 5eb8d462..92be5afd 100644 --- a/src/test/kotlin/sirius/kernel/commons/FilesTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/FilesTest.kt @@ -11,6 +11,9 @@ import org.junit.jupiter.api.Test import kotlin.test.assertEquals import kotlin.test.assertNull +/** + * Tests the [Files] class. + */ internal class FilesTest { @Test diff --git a/src/test/kotlin/sirius/kernel/commons/HasherTest.kt b/src/test/kotlin/sirius/kernel/commons/HasherTest.kt new file mode 100644 index 00000000..32d4990d --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/HasherTest.kt @@ -0,0 +1,47 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons + +import org.junit.jupiter.api.Test +import java.io.File +import kotlin.test.assertEquals + +/** + * Tests the [Hasher] class. + */ +class HasherTest { + + @Test + fun testMD5() { + assertEquals("b10a8db164e0754105b7a99be72e3fe5", Hasher.md5().hash("Hello World").toHexString()) + assertEquals("sQqNsWTgdUEFt6mb5y4/5Q==", Hasher.md5().hash("Hello World").toBase64String()) + assertEquals( + "e59ff97941044f85df5297e1c302d260", + Hasher.md5() + .hashFile(File(javaClass.getResource("/hash_test_file.txt")?.toURI() ?: error(""))) + .toHexString() + ) + } + + @Test + fun testSHA() { + assertEquals( + "0a4d55a8d778e5022fab701977c5d840bbc486d0", + Hasher.sha1().hash("Hello World").toHexString() + ) + assertEquals( + "a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e", + Hasher.sha256().hash("Hello World").toHexString() + ) + assertEquals( + "2c74fd17edafd80e8447b0d46741ee243b7eb74dd2149a0ab1b9246fb30382f27e853d8585719e0e67cbda0daa8f51671064615d645ae27acb15bfb1447f459b", + Hasher.sha512().hash("Hello World").toHexString() + ) + } +} diff --git a/src/test/kotlin/sirius/kernel/commons/JsonTest.kt b/src/test/kotlin/sirius/kernel/commons/JsonTest.kt index 23ab13d2..7cebacd3 100644 --- a/src/test/kotlin/sirius/kernel/commons/JsonTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/JsonTest.kt @@ -22,7 +22,9 @@ import java.util.* import kotlin.test.assertEquals import kotlin.test.assertTrue - +/** + * Tests the [Json] class. + */ class JsonTest { @Test diff --git a/src/test/kotlin/sirius/kernel/commons/MultiMapTest.kt b/src/test/kotlin/sirius/kernel/commons/MultiMapTest.kt new file mode 100644 index 00000000..ee69f202 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/MultiMapTest.kt @@ -0,0 +1,40 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons + +import org.junit.jupiter.api.Test +import kotlin.test.assertContentEquals + +/** + * Tests the [MultiMap] class. + */ +class MultiMapTest { + + @Test + fun test() { + val underTest = MultiMap.create() + underTest.put("A", "A") + underTest.put("A", "B") + assertContentEquals(listOf("A", "B"), underTest["A"]) + underTest.put("A", "B") + assertContentEquals(listOf("A", "B", "B"), underTest["A"]) + underTest.remove("A", "B") + assertContentEquals(listOf("A"), underTest["A"]) + underTest.put("B", "A") + underTest.put("B", "C") + assertContentEquals(listOf("A", "B"), underTest.keySet()) + assertContentEquals(listOf("A", "A", "C"), underTest.values()) + underTest.underlyingMap.clear() + assertContentEquals(listOf(), underTest["A"]) + underTest.put("B", "C") + assertContentEquals(listOf("C"), underTest["B"]) + underTest.clear() + assertContentEquals(listOf(), underTest["B"]) + } +} diff --git a/src/test/kotlin/sirius/kernel/commons/PriorityCollectorTest.kt b/src/test/kotlin/sirius/kernel/commons/PriorityCollectorTest.kt new file mode 100644 index 00000000..6cf07239 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/PriorityCollectorTest.kt @@ -0,0 +1,32 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons + +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +/** + * Tests the [PriorityCollector] class. + */ +class PriorityCollectorTest { + + @Test + fun test() { + val collector = PriorityCollector.create() + collector.addDefault("B") + collector.add(50, "A") + collector.add(101, "C") + + assertEquals(mutableListOf("A", "B", "C"), collector.getData()) + + collector.getData().clear() + + assertEquals(3, collector.size()) + } +} diff --git a/src/test/kotlin/sirius/kernel/commons/StreamsTest.kt b/src/test/kotlin/sirius/kernel/commons/StreamsTest.kt new file mode 100644 index 00000000..fd61d7dd --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/StreamsTest.kt @@ -0,0 +1,41 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons + +import org.junit.jupiter.api.Test +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.nio.charset.StandardCharsets +import kotlin.test.assertEquals + +/** + * Tests the [Streams] class. + */ +class StreamsTest { + + @Test + fun transferTest() { + val testString = "Hello from the other side..." + val source = ByteArrayInputStream(testString.toByteArray(StandardCharsets.UTF_8)) + val target = ByteArrayOutputStream() + Streams.transfer(source, target) + assertEquals(testString, String(target.toByteArray(), StandardCharsets.UTF_8)) + } + + @Test + fun largeTransferTest() { + val builder = StringBuilder() + builder.append("Hello World".repeat(10000)) + val testString = builder.toString() + val source = ByteArrayInputStream(testString.toByteArray(StandardCharsets.UTF_8)) + val target = ByteArrayOutputStream() + Streams.transfer(source, target) + assertEquals(testString, String(target.toByteArray(), StandardCharsets.UTF_8)) + } +} diff --git a/src/test/kotlin/sirius/kernel/commons/StringsTest.kt b/src/test/kotlin/sirius/kernel/commons/StringsTest.kt new file mode 100644 index 00000000..ed895192 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/StringsTest.kt @@ -0,0 +1,297 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons + +import org.junit.jupiter.api.Test +import java.util.* +import java.util.function.UnaryOperator +import java.util.regex.Pattern +import kotlin.test.* + +/** + * Tests the [Strings] class. + */ +class StringsTest { + + @Test + fun isFilled() { + assertTrue { Strings.isFilled("Test") } + assertTrue { Strings.isFilled(" ") } + assertFalse { Strings.isFilled(null) } + assertFalse { Strings.isFilled("") } + } + + @Test + fun isEmpty() { + assertFalse { Strings.isEmpty("Test") } + assertFalse { Strings.isEmpty(" ") } + assertTrue { Strings.isEmpty(null) } + assertTrue { Strings.isEmpty("") } + } + + @Test + fun equalIgnoreCase() { + assertTrue { Strings.equalIgnoreCase("A", "a") } + assertFalse { Strings.equalIgnoreCase("A", "b") } + assertTrue { Strings.equalIgnoreCase("", null) } + assertFalse { Strings.equalIgnoreCase(" ", null) } + assertTrue { Strings.equalIgnoreCase(null, null) } + } + + @Test + fun areEqual() { + assertTrue { Strings.areEqual("A", "A") } + assertFalse { Strings.areEqual("a", "A") } + assertFalse { Strings.areEqual("a", "A") } + assertTrue { + Strings.areEqual( + "a", "A" + ) { x: Any -> x.toString().lowercase(Locale.getDefault()) } + } + assertTrue { Strings.areEqual("", null) } + assertFalse { Strings.areEqual(" ", null) } + assertTrue { Strings.areEqual(null, null) } + } + + @Test + fun areTrimmedEqual() { + assertTrue { Strings.areTrimmedEqual("A ", "A") } + assertFalse { Strings.areTrimmedEqual("a", "A ") } + assertTrue { Strings.areTrimmedEqual("", null) } + assertTrue { Strings.areTrimmedEqual(" ", null) } + assertTrue { Strings.areTrimmedEqual(null, null) } + } + + @Test + fun toStringMethod() { + assertEquals("A", Strings.toString("A")) + assertEquals("", Strings.toString("")) + assertNull(Strings.toString(null)) + } + + @Test + fun apply() { + assertEquals("B A", Strings.apply("%s A", "B")) + assertEquals("A null", Strings.apply("A %s", null as String?)) + } + + @Test + fun firstFilled() { + assertEquals("A", Strings.firstFilled("A")) + assertEquals("A", Strings.firstFilled("A", "B")) + assertEquals("A", Strings.firstFilled(null, "A")) + assertEquals("A", Strings.firstFilled("", "A")) + assertEquals("A", Strings.firstFilled(null, null, "A")) + assertNull(Strings.firstFilled()) + assertNull(Strings.firstFilled(null as String?)) + assertNull(Strings.firstFilled("")) + } + + @Test + fun urlEncode() { + assertEquals("A%3FTEST%26B%C3%84%C3%96%C3%9C", Strings.urlEncode("A?TEST&BÄÖÜ")) + } + + @Test + fun urlDecode() { + assertEquals("A?TEST&BÄÖÜ", Strings.urlDecode("A%3FTEST%26B%C3%84%C3%96%C3%9C")) + } + + @Test + fun split() { + assertEquals(Tuple.create("A", "B"), Strings.split("A|B", "|")) + assertEquals(Tuple.create("A", "&B"), Strings.split("A&&B", "&")) + assertEquals(Tuple.create("A", "B"), Strings.split("A&&B", "&&")) + assertEquals(Tuple.create("A", ""), Strings.split("A|", "|")) + assertEquals(Tuple.create("", "B"), Strings.split("|B", "|")) + assertEquals(Tuple.create("A&B", null), Strings.split("A&B", "|")) + } + + @Test + fun splitSmart() { + assertEquals(listOf("a"), Strings.splitSmart("a", 2)) + assertEquals(listOf(), Strings.splitSmart(null, 0)) + assertEquals(listOf(), Strings.splitSmart(null, 2)) + assertEquals(listOf(), Strings.splitSmart("", 0)) + assertEquals(listOf(), Strings.splitSmart("", 2)) + assertEquals(listOf("das ist", "ein", "Test"), Strings.splitSmart("das ist ein Test", 7)) + assertEquals( + listOf("lange-w", "örter-w", "erden-a", "uch-get", "rennt"), + Strings.splitSmart("lange-wörter-werden-auch-getrennt", 7) + ) + assertEquals( + listOf("Ein langer Text kann in eine Zeile"), + Strings.splitSmart("Ein langer Text kann in eine Zeile", 40) + ) + } + + @Test + fun join() { + assertEquals("A,B,C", Strings.join(",", "A", "B", "C")) + assertEquals("A,C", Strings.join(",", "A", null, "", "C")) + assertEquals("A", Strings.join(",", "A")) + assertEquals("", Strings.join(",")) + assertEquals("ABC", Strings.join("", "A", "B", "C")) + } + + @Test + fun replaceAll() { + assertEquals("A<B&C&&D&;&E", Strings.replaceAll( + Pattern.compile("&([a-zA-Z0-9]{0,6};?)"), "A<B&C&&D&;&E" + ) { s: String -> (if (s.endsWith(";") && !s.startsWith(";")) "&" else "&") + s }) + } + + @Test + fun leftPad() { + assertEquals(" A", Strings.leftPad("A", " ", 4)) + assertEquals(" A", Strings.leftPad("A", " ", 5)) + assertEquals(" A", Strings.leftPad("A", " ", 4)) + assertEquals("AAA", Strings.leftPad("AAA", " ", 2)) + } + + @Test + fun rightPad() { + assertEquals("A ", Strings.rightPad("A", " ", 4)) + assertEquals("A ", Strings.rightPad("A", " ", 5)) + assertEquals("A ", Strings.rightPad("A", " ", 4)) + assertEquals("AAA", Strings.rightPad("AAA", " ", 2)) + } + + @Test + fun reduceCharacters() { + assertEquals("Hello", StringCleanup.reduceCharacters("Hello")) + assertSame("Hello", StringCleanup.reduceCharacters("Hello")) + assertEquals("Hello", StringCleanup.reduceCharacters("Héllo")) + assertEquals("AOEO", StringCleanup.reduceCharacters("AÖO")) + assertEquals("AEAAE", StringCleanup.reduceCharacters("ÄAÄ")) + } + + @Test + fun cleanup() { + assertEquals( + "Hel lo", Strings.cleanup("Hel lo ", UnaryOperator { input: String? -> StringCleanup.trim(input!!) }) + ) + assertEquals( + "Hel lo ", + Strings.cleanup( + "Hel \t \t \r\n lo ", + UnaryOperator { input: String? -> StringCleanup.reduceWhitespace(input!!) }) + ) + assertEquals( + "Hello", + Strings.cleanup( + "Hel \t \t \n lo ", + UnaryOperator { input: String? -> StringCleanup.removeWhitespace(input!!) }) + ) + assertEquals( + "Hello", + Strings.cleanup("Héllo", UnaryOperator { term: String? -> StringCleanup.reduceCharacters(term) }) + ) + assertEquals( + "hello", Strings.cleanup("Héllo", + UnaryOperator { term: String? -> StringCleanup.reduceCharacters(term) }, + UnaryOperator { input: String? -> StringCleanup.lowercase(input!!) }) + ) + assertEquals( + "HELLO", Strings.cleanup("Héllo", + UnaryOperator { term: String? -> StringCleanup.reduceCharacters(term) }, + UnaryOperator { input: String? -> StringCleanup.uppercase(input!!) }) + ) + assertEquals( + "Hello", + Strings.cleanup("Hel-lo", UnaryOperator { input: String? -> StringCleanup.removePunctuation(input!!) }) + ) + assertEquals( + "Hello", + Strings.cleanup( + "\u0008Hello", + UnaryOperator { input: String? -> StringCleanup.removeControlCharacters(input!!) }) + ) + assertEquals( + "Test", Strings.cleanup("Test", + UnaryOperator { input: String? -> StringCleanup.replaceXml(input) }, + UnaryOperator { input: String? -> StringCleanup.trim(input!!) }) + ) + assertEquals( + "Test", Strings.cleanup("Test
", + UnaryOperator { input: String? -> StringCleanup.replaceXml(input) }, + UnaryOperator { input: String? -> StringCleanup.trim(input!!) }) + ) + assertEquals( + "Test Blubb", Strings.cleanup("Test
Blubb
", + UnaryOperator { input: String? -> StringCleanup.replaceXml(input) }, + UnaryOperator { input: String? -> StringCleanup.trim(input!!) }) + ) + assertEquals( + "foo having < 3 m, with >= 3 m", Strings.cleanup("foo having < 3 m, with >= 3 m", + UnaryOperator { input: String? -> StringCleanup.replaceXml(input) }, + UnaryOperator { input: String? -> StringCleanup.trim(input!!) }) + ) + assertEquals( + "<b>Foo <br /> Bar</b>", + Strings.cleanup("Foo
Bar
", UnaryOperator { input: String? -> + StringCleanup.escapeXml( + input + ) + }) + ) + assertEquals( + "Hello
World", + Strings.cleanup("Hello\nWorld", UnaryOperator { input: String? -> StringCleanup.nlToBr(input) }) + ) + assertEquals( + "Testalert('Hello World!')", Strings.cleanup("Test", + UnaryOperator { input: String? -> StringCleanup.removeXml(input!!) }) + ) + assertEquals( + " äöüÄÖÜß<>\"'&* * * * * * ", + Strings.cleanup(" äöüÄÖÜß<>"'&••‣‣⁃⁃", + UnaryOperator { input: String? -> StringCleanup.decodeHtmlEntities(input!!) }) + ) + } + + @Test + fun probablyContainsXml() { + assertTrue(Strings.probablyContainsXml("Test")) + assertTrue(Strings.probablyContainsXml("
")) + assertTrue(Strings.probablyContainsXml("
")) + assertTrue(Strings.probablyContainsXml("
")) + assertTrue(Strings.probablyContainsXml("")) + assertTrue(Strings.probablyContainsXml("")) + assertFalse(Strings.probablyContainsXml("foo having < 3 m, with >= 3 m")) + assertFalse(Strings.probablyContainsXml("foo having <3 m, with > 3 m")) + } + + @Test + fun containsAllowedHtml() { + assertTrue(Strings.containsAllowedHtml("Test")) + assertTrue(Strings.containsAllowedHtml("
")) + assertTrue(Strings.containsAllowedHtml("
")) + assertTrue(Strings.containsAllowedHtml("
")) + assertTrue(Strings.containsAllowedHtml("")) + assertTrue(Strings.containsAllowedHtml("

")) + assertFalse(Strings.containsAllowedHtml("")) + assertFalse(Strings.containsAllowedHtml("Test ")) + assertFalse(Strings.containsAllowedHtml("foo having < 3 m, with >= 3 m")) + assertFalse(Strings.containsAllowedHtml("foo having <3 m, with > 3 m")) + } + + @Test + fun limit() { + assertEquals("", Strings.limit(null, 10, false)) + assertEquals("", Strings.limit(null, 10, true)) + assertEquals("", Strings.limit("", 10, false)) + assertEquals("", Strings.limit("", 10, true)) + assertEquals("ABCDE", Strings.limit("ABCDE", 10, false)) + assertEquals("ABCDE", Strings.limit("ABCDE", 10, true)) + assertEquals("ABCDEFGHIJ", Strings.limit("ABCDEFGHIJKLMNOP", 10, false)) + assertEquals("ABCDEFGHI…", Strings.limit("ABCDEFGHIJKLMNOP", 10, true)) + } + +} diff --git a/src/test/kotlin/sirius/kernel/commons/TrieTest.kt b/src/test/kotlin/sirius/kernel/commons/TrieTest.kt new file mode 100644 index 00000000..e6817a6d --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/TrieTest.kt @@ -0,0 +1,68 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons + +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue + +/** + * Tests the [Trie] class. + */ +class TrieTest { + @Test + fun isFilled() { + val check = "I'd like to have three beer please" + val iter = trie.iterator() + var found = 0 + for (index in check.indices) { + if (!iter.doContinue(check[index])) { + if (iter.isCompleted()) { + found = iter.getValue() + } + iter.resetWith(check[index]) + } + } + assertEquals(5, found) + assertEquals(2, trie["on"] as Int) + assertNull(trie["onx"]) + assertTrue { trie.containsKey("thrae") } + assertFalse { trie.containsKey("thre") } + } + + @Test + fun keySet() { + assertEquals(7, trie.size()) + assertEquals(setOf("one", "on", "one1", "two", "three", "thrae", "th"), trie.keySet()) + assertEquals(setOf("one", "on", "one1", "two", "three", "thrae", "th"), trie.getAllKeysBeginningWith("")) + assertEquals(setOf("one", "on", "one1"), trie.getAllKeysBeginningWith("on")) + assertEquals(setOf("three"), trie.getAllKeysBeginningWith("three")) + assertEquals(0, trie.getAllKeysBeginningWith("threee").size) + } + + companion object { + private lateinit var trie: Trie + + @JvmStatic + @BeforeAll + fun createTrie() { + trie = Trie.create() + trie.put("one", 1) + trie.put("on", 2) + trie.put("one1", 3) + trie.put("two", 4) + trie.put("three", 5) + trie.put("thrae", 6) + trie.put("th", 7) + } + } +} diff --git a/src/test/kotlin/sirius/kernel/commons/ValuesTest.kt b/src/test/kotlin/sirius/kernel/commons/ValuesTest.kt new file mode 100644 index 00000000..553f2ac8 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/ValuesTest.kt @@ -0,0 +1,38 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons + +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse + +/** + * Tests the [Values] class. + */ +internal class ValuesTest { + @Test + fun at() { + assertEquals("A", Values.of(arrayOf("A", "B", "C")).at(0).asString()) + assertFalse { Values.of(arrayOf("A", "B", "C")).at(10).isFilled } + } + + @Test + fun excelStyleColumns() { + assertEquals("A", Values.of(arrayOf("A", "B", "C")).at("A").asString()) + assertEquals("C", Values.of(arrayOf("A", "B", "C")).at("C").asString()) + + val test: MutableList = ArrayList() + for (i in 1..99) { + test.add(i.toString()) + } + + assertEquals("28", Values.of(test).at("AB").asString()) + assertEquals("34", Values.of(test).at("AH").asString()) + } +} diff --git a/src/test/kotlin/sirius/kernel/health/ExceptionsTest.kt b/src/test/kotlin/sirius/kernel/health/ExceptionsTest.kt new file mode 100644 index 00000000..66bf6191 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/health/ExceptionsTest.kt @@ -0,0 +1,51 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.health + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import sirius.kernel.SiriusExtension +import java.util.logging.Level +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +/** + * Tests the [Exceptions] class. + */ +@ExtendWith(SiriusExtension::class) +internal class ExceptionsTest { + @Test + fun `Warning is logged when a deprecated method is called`() { + LogHelper.clearMessages() + caller() + assertTrue { + LogHelper.hasMessage( + Level.WARNING, + Exceptions.DEPRECATION_LOG, + "^The deprecated method 'sirius.kernel.health.ExceptionsTest.deprecatedMethod'" + + " was called by 'sirius.kernel.health.ExceptionsTest.caller'" + ) + } + } + + private fun caller() { + deprecatedMethod() + } + + private fun deprecatedMethod() { + Exceptions.logDeprecatedMethodUse() + } + + @Test + fun `root cause remains when exception is handled`() { + val root = Exceptions.createHandled().withSystemErrorMessage("Root Cause").handle() + val exception = Exceptions.handle(Exception(IllegalArgumentException(root))) + assertEquals(root, Exceptions.getRootCause(exception)) + } +} diff --git a/src/test/kotlin/sirius/kernel/health/LogHelper.kt b/src/test/kotlin/sirius/kernel/health/LogHelper.kt new file mode 100644 index 00000000..4bccce97 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/health/LogHelper.kt @@ -0,0 +1,64 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.health + +import sirius.kernel.di.std.Part +import java.util.logging.Level +import java.util.regex.Pattern + +/** + * Provides a helper for checking whether a certain log message was logged. + */ +object LogHelper { + + + @Part + private lateinit var monitor: MemoryBasedHealthMonitor + + /** + * Clears all log messages. + * + * + * It is probably a good idea to clear all previously logged messages prior to a test. + */ + fun clearMessages() { + monitor.messages.clear() + } + + /** + * Determines if a message matching the given criteria was logged. + * + * @param level the expected level + * @param logger the expected target logger + * @param pattern the **regular expression** to **find** within the message. Use ^foobar$ to match + * the whole message. + * @return true if a matching message was found, false otherwise + */ + private fun hasMessage(level: Level, logger: String, pattern: String): Boolean { + val regEx = Pattern.compile(pattern) + + return monitor.messages.stream().filter { logger == it.receiver.name } + .filter { level === it.logLevel } + .filter { regEx.matcher(it.message).find() } + .findFirst().isPresent + } + + /** + * Determines if a message matching the given criteria was logged. + * + * @param level the expected level + * @param logger the expected target logger + * @param pattern the **regular expression** to **find** within the message. Use ^foobar$ to match + * the whole message. + * @return true if a matching message was found, false otherwise + */ + fun hasMessage(level: Level, logger: Log, pattern: String): Boolean { + return hasMessage(level, logger.name, pattern) + } +} diff --git a/src/test/kotlin/sirius/kernel/nls/FormatterTest.kt b/src/test/kotlin/sirius/kernel/nls/FormatterTest.kt new file mode 100644 index 00000000..8ea379d4 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/nls/FormatterTest.kt @@ -0,0 +1,121 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.nls + +import org.junit.Assert.assertThrows +import org.junit.jupiter.api.Test +import sirius.kernel.commons.Context +import kotlin.test.assertEquals + +/** + * Tests the [Formatter] class. + */ +class FormatterTest { + + @Test + fun `format replaces parameters`() { + val pattern = "Test \${foo}" + + val result = Formatter.create(pattern).set("foo", "bar").format() + + assertEquals("Test bar", result) + } + + @Test + fun `set trims and calls toUserString on parameters`() { + val pattern = "Test \${foo} \${bar}" + + val result = Formatter.create(pattern).set("foo", true).set("bar", " test ").format() + + assertEquals("Test NLS.yes test", result) + } + + @Test + fun `setDirect neither trims nor calls toUserString on parameters`() { + val pattern = "Test \${foo} \${bar}" + + val result = Formatter.create(pattern).setDirect(Context.create().set("foo", true)).setDirect("bar", " test ") + .format() + + assertEquals("Test true test ", result) + } + + @Test + fun `smartFormat skips empty block`() { + val pattern = "Test[ \${foo}]" + + val result = Formatter.create(pattern).set("foo", null).smartFormat() + + assertEquals("Test", result) + } + + @Test + fun `smartFormat accepts nested blocks`() { + val pattern = "Test[[ \${foo}] bar \${baz}]" + + val result1 = Formatter.create(pattern).set("foo", null).set("baz", null).smartFormat() + val result2 = Formatter.create(pattern).set("foo", "foo").set("baz", null).smartFormat() + val result3 = Formatter.create(pattern).set("foo", null).set("baz", "baz").smartFormat() + val result4 = Formatter.create(pattern).set("foo", "foo").set("baz", "baz").smartFormat() + + assertEquals("Test", result1) + assertEquals("Test foo bar ", result2) + assertEquals("Test bar baz", result3) + assertEquals("Test foo bar baz", result4) + } + + @Test + fun `format fails when using unknown parameter`() { + val pattern = "Test \${foo}" + + assertThrows(IllegalArgumentException::class.java) { + Formatter.create(pattern).smartFormat() + } + } + + @Test + fun `format fails for missing curly bracket`() { + val pattern = "Test \${foo" + + assertThrows(IllegalArgumentException::class.java) { + Formatter.create(pattern).smartFormat() + } + } + + @Test + fun `format fails for missing square bracket`() { + val pattern = "Test [\${foo}" + + assertThrows(IllegalArgumentException::class.java) { + Formatter.create(pattern).smartFormat() + } + } + + @Test + fun `smartFormat fails for additional square bracket`() { + val pattern = "Test [\${foo}]]" + + assertThrows(IllegalArgumentException::class.java) { + Formatter.create(pattern).smartFormat() + } + } + + @Test + fun `createJSFormatter works`() { + val pattern = "foo = '\${foo}' bar = \"\${bar}\"" + + val result = Formatter.createJSFormatter(pattern).set("foo", "d'or").set("bar", "\"buzz\"").format() + + assertEquals( + """ + foo = 'd\'or' bar = "\"buzz\"" + """.trimIndent(), result + ) + } +} diff --git a/src/test/kotlin/sirius/kernel/settings/ConfigBuilderTest.kt b/src/test/kotlin/sirius/kernel/settings/ConfigBuilderTest.kt new file mode 100644 index 00000000..94b02428 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/settings/ConfigBuilderTest.kt @@ -0,0 +1,76 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.settings + +import kotlin.test.Test +import kotlin.test.assertEquals + +class ConfigBuilderTest { + + @Test + fun `Config builder works`() { + val configBuilder = ConfigBuilder() + configBuilder.addVariable("scope.foo", "true") + assertEquals(//language=HOCON + "scope.foo = true", + configBuilder.toString() + ) + } + + @Test + fun `Scope unfolding works`() { + val configBuilder = ConfigBuilder() + configBuilder.addVariable("scope.foo", "true") + configBuilder.addVariable("scope.bar", "false") + assertEquals(//language=HOCON + """ + scope { + foo = true + bar = false + } + """.trimIndent(), + configBuilder.toString() + ) + } + + @Test + fun `Complex example works`() { + val configBuilder = ConfigBuilder() + configBuilder.addVariable("scopeA.foo", "true") + configBuilder.addVariable("scopeA.bar", "true") + configBuilder.addVariable("scopeB.foo", "\"enabled\"") + configBuilder.addVariable("scopeB.scopeC.foo", "\"disabled\"") + configBuilder.addVariable("scopeB.scopeC.bar", "\"enabled\"") + configBuilder.addVariable("foo", "\"disabled\"") + configBuilder.addVariable("foo.bar.test", "42") + + assertEquals(//language=HOCON + """ + foo = "disabled" + foo.bar.test = 42 + + scopeA { + foo = true + bar = true + } + + scopeB { + foo = "enabled" + + scopeC { + foo = "disabled" + bar = "enabled" + } + } + """.trimIndent(), + configBuilder.toString() + ) + } + +} diff --git a/src/test/kotlin/sirius/kernel/settings/SettingsTest.kt b/src/test/kotlin/sirius/kernel/settings/SettingsTest.kt new file mode 100644 index 00000000..22fded52 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/settings/SettingsTest.kt @@ -0,0 +1,70 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.settings + +import com.typesafe.config.ConfigFactory +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import sirius.kernel.Sirius +import sirius.kernel.SiriusExtension +import kotlin.test.assertEquals +import kotlin.test.assertNull + +/** + * Tests the [Settings] class. + */ +@ExtendWith(SiriusExtension::class) +class SettingsTest { + + @Test + fun `Inner configs are delivered in their given order`() { + val keys = Sirius.getSettings().getConfigs("test-configs").stream().map { it.getString("value") }.toList() + assertEquals(listOf("2", "1", "3"), keys) + } + + @Test + fun `Inner configs are delivered as sorted by their priority if given`() { + val keys = + Sirius.getSettings().getConfigs("test-configs-sorted").stream().map { it.getString("value") }.toList() + assertEquals(listOf("1", "2", "3"), keys) + } + + @Test + fun `No exception is thrown for retrieving a non-existent extension, even when settings are strict`() { + val extension = Sirius.getSettings().getExtension("non-existent", "not-specified") + assertNull(extension) + } + + @Test + fun `Settings#getTranslatedString works as expected`() { + val settings = Settings( + ConfigFactory.parseString(//language=HOCON + """ + directKey = "test" + intKey = 5 + translatedKey = "${'$'}testKey" + mapKey { + en: test + de: "ein Test" + default: "fallback" + } + """ + ), false + ) + + assertEquals("", settings.getTranslatedString("unknown", "en")) + assertEquals("test", settings.getTranslatedString("directKey", "en")) + assertEquals("testKey", settings.getTranslatedString("translatedKey", "en")) + assertEquals("5", settings.getTranslatedString("intKey", "en")) + assertEquals("test", settings.getTranslatedString("mapKey", "en")) + assertEquals("ein Test", settings.getTranslatedString("mapKey", "de")) + assertEquals("fallback", settings.getTranslatedString("mapKey", "xx")) + } + +} diff --git a/src/test/kotlin/sirius/kernel/testutil/Reflections.kt b/src/test/kotlin/sirius/kernel/testutil/Reflections.kt index 0debf483..33741981 100644 --- a/src/test/kotlin/sirius/kernel/testutil/Reflections.kt +++ b/src/test/kotlin/sirius/kernel/testutil/Reflections.kt @@ -26,7 +26,8 @@ class Reflections { */ fun callPrivateMethod(instance: T, functionName: String, vararg args: Any): Any? { try { - val function = instance.javaClass.getDeclaredMethod(functionName, *args.map { it.javaClass }.toTypedArray()) + val function = + instance.javaClass.getDeclaredMethod(functionName, *args.map { it.javaClass }.toTypedArray()) function.trySetAccessible() return function.invoke(instance, *args) } catch (e: InvocationTargetException) { diff --git a/src/test/kotlin/sirius/kernel/xml/ContentDispositionParserTest.kt b/src/test/kotlin/sirius/kernel/xml/ContentDispositionParserTest.kt new file mode 100644 index 00000000..9dae2651 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/xml/ContentDispositionParserTest.kt @@ -0,0 +1,38 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.xml + +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource +import kotlin.test.assertEquals + +/** + * Tests the [ContentDispositionParser] class. + */ +class ContentDispositionParserTest { + + @ParameterizedTest + @CsvSource( + delimiter = '|', textBlock = """ + attachment; filename="test.pdf" | test.pdf + inline; filename=test.pdf | test.pdf + attachment; filename=test.pdf ; size="2000" | test.pdf + attachment; size="2000" ; filename=test.pdf | test.pdf + attachment; filename="test pdf doc.pdf" | test pdf doc.pdf + inline; filename*=UTF-8''test.pdf | test.pdf + inline; filename*="UTF-8''test.pdf" | test.pdf + inline; filename*="UTF-8''test%20pdf%20doc.pdf" | test pdf doc.pdf + inline; filename*=UTF-8''test%20pdf%20doc.pdf | test pdf doc.pdf + attachment; filename*=iso-8859-1'en'file%27%20%27name.jpg | file' 'name.jpg + """ + ) + fun `parseContentDisposition regex works for various scenarios`(input: String, output: String) { + assertEquals(output, ContentDispositionParser.parseFileName(input).get()) + } +} diff --git a/src/test/kotlin/sirius/kernel/xml/XmlReaderTest.kt b/src/test/kotlin/sirius/kernel/xml/XmlReaderTest.kt new file mode 100644 index 00000000..de820ea6 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/xml/XmlReaderTest.kt @@ -0,0 +1,141 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.xml + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import sirius.kernel.SiriusExtension +import sirius.kernel.commons.ValueHolder +import sirius.kernel.health.Counter +import java.io.ByteArrayInputStream +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +/** + * Tests the [XMLReader] class. + */ +@ExtendWith(SiriusExtension::class) +internal class XmlReaderTest { + @Test + fun `XMLReader extracts XPATH expression`() { + val readString = ValueHolder.of(null) + val nodeCount = Counter() + val reader = XMLReader() + reader.addHandler("test") { node: StructuredNode -> + nodeCount.inc() + readString.set(node.queryString("value")) + } + + reader.parse( + ByteArrayInputStream(//language=xml + """ + + + 1 + + + 2 + + + 5 + + + """.trimIndent().toByteArray() + ) + ) + + assertEquals("5", readString.get()) + assertEquals(3, nodeCount.count, "parsed invalid count of nodes") + } + + @Test + fun `XMLReader supports compound XPATH paths`() { + val shouldToggle = ValueHolder.of(false) + val shouldNotToggle = ValueHolder.of(false) + val reader = XMLReader() + reader.addHandler("doc/test/value") { node: StructuredNode? -> + shouldToggle.set( + true + ) + } + reader.addHandler("value") { node: StructuredNode? -> shouldNotToggle.set(true) } + + reader.parse( + ByteArrayInputStream(//language=xml + """ + + + 1 + + + 2 + + + 5 + + + """.trimIndent().toByteArray() + ) + ) + + assertTrue { shouldToggle.get() } + assertFalse { shouldNotToggle.get() } + } + + @Test + fun `XMLReader reads attributes`() { + val attributes: MutableMap = HashMap() + val attribute = ValueHolder.of("") + val reader = XMLReader() + reader.addHandler("test") { node: StructuredNode -> + attributes.putAll(node.getAttributes()) + attribute.set(node.getAttribute("namedAttribute").asString()) + } + + reader.parse( + ByteArrayInputStream(//language=xml + """ + + 1 + + """.trimIndent().toByteArray() + ) + ) + + reader.parse(ByteArrayInputStream("1".toByteArray())) + + assertEquals(2, attributes.size) + assertEquals("abc", attribute.get()) + } + + @Test + fun `Reading non existing attributes does not throw errors`() { + val attributes: MutableMap = HashMap() + val attribute = ValueHolder.of("wrongValue") + val reader = XMLReader() + reader.addHandler("test") { node: StructuredNode -> + attributes.putAll(node.getAttributes()) + attribute.set(node.getAttribute("namedAttribute").asString()) + } + + reader.parse( + ByteArrayInputStream(//language=xml + """ + + 1 + + """.trimIndent().toByteArray() + ) + ) + + assertEquals(0, attributes.size) + assertEquals("", attribute.get()) + } +} From 30501caf820a53fca938d26a0ed05c222e325742 Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Fri, 28 Jul 2023 16:24:04 +0200 Subject: [PATCH 017/111] Removes failing extra empty test execution The line break at the end of the multiline string resulted in jUnit executing the test one more time without any parameters. Fixes: OX-10305 --- .../kotlin/sirius/kernel/xml/ContentDispositionParserTest.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/kotlin/sirius/kernel/xml/ContentDispositionParserTest.kt b/src/test/kotlin/sirius/kernel/xml/ContentDispositionParserTest.kt index 9dae2651..117de8ba 100644 --- a/src/test/kotlin/sirius/kernel/xml/ContentDispositionParserTest.kt +++ b/src/test/kotlin/sirius/kernel/xml/ContentDispositionParserTest.kt @@ -29,8 +29,7 @@ class ContentDispositionParserTest { inline; filename*="UTF-8''test.pdf" | test.pdf inline; filename*="UTF-8''test%20pdf%20doc.pdf" | test pdf doc.pdf inline; filename*=UTF-8''test%20pdf%20doc.pdf | test pdf doc.pdf - attachment; filename*=iso-8859-1'en'file%27%20%27name.jpg | file' 'name.jpg - """ + attachment; filename*=iso-8859-1'en'file%27%20%27name.jpg | file' 'name.jpg""" ) fun `parseContentDisposition regex works for various scenarios`(input: String, output: String) { assertEquals(output, ContentDispositionParser.parseFileName(input).get()) From e9811cf50f5cdd7387d96b3fae25e47272d71a81 Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 31 Jul 2023 13:47:45 +0200 Subject: [PATCH 018/111] Ensures test runs in sirius context This test checks whether a translation is used in an expected result. Previously checking on the key would work when the test is run separately, but would fail when it is run inside the test suite where sirius is running. Fixes: OX-10305 --- src/test/kotlin/sirius/kernel/nls/FormatterTest.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/kotlin/sirius/kernel/nls/FormatterTest.kt b/src/test/kotlin/sirius/kernel/nls/FormatterTest.kt index 8ea379d4..ebed00c7 100644 --- a/src/test/kotlin/sirius/kernel/nls/FormatterTest.kt +++ b/src/test/kotlin/sirius/kernel/nls/FormatterTest.kt @@ -10,12 +10,15 @@ package sirius.kernel.nls import org.junit.Assert.assertThrows import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import sirius.kernel.SiriusExtension import sirius.kernel.commons.Context import kotlin.test.assertEquals /** * Tests the [Formatter] class. */ +@ExtendWith(SiriusExtension::class) class FormatterTest { @Test @@ -33,7 +36,7 @@ class FormatterTest { val result = Formatter.create(pattern).set("foo", true).set("bar", " test ").format() - assertEquals("Test NLS.yes test", result) + assertEquals("Test Ja test", result) } @Test From 7b4caf6ff7c3f1428aadeb197991f11af97b732c Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 1 Aug 2023 10:25:12 +0200 Subject: [PATCH 019/111] =?UTF-8?q?Fixes=20documentation=20=E2=9C=8D?= =?UTF-8?q?=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/sirius/kernel/commons/Trie.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/sirius/kernel/commons/Trie.java b/src/main/java/sirius/kernel/commons/Trie.java index 70ff3511..adab7063 100644 --- a/src/main/java/sirius/kernel/commons/Trie.java +++ b/src/main/java/sirius/kernel/commons/Trie.java @@ -27,16 +27,16 @@ * given text, we can do the following: *
  * {@code
- * Trie<Boolean> trie = Trie.create();
+ * Trie trie = Trie.create();
  *
  * trie.put("one", true);
  * trie.put("two", true);
  * trie.put("three", true);
  *
  * String check = "I'd like to have three beer please";
- * Trie.ContainmentIterator<Boolean> iter = trie.iterator();
+ * Trie.ContainmentIterator iter = trie.iterator();
  *
- * for(int i = 0; i < check.length(); i++) {
+ * for(int i = 0; i < check.length(); i++) {
  *     if (!iter.doContinue(check.charAt(i))) {
  *         if (iter.isCompleted()) {
  *             System.out.println("Found!");
@@ -145,7 +145,7 @@ public interface ContainmentIterator {
         void reset();
 
         /**
-         * Restarts the iterator at  the beginning and tries to perform the next transition using the given character.
+         * Restarts the iterator at the beginning and tries to perform the next transition using the given character.
          *
          * @param c the character to try to use after resetting the iterator
          * @return true if the transition using c was possible, false otherwise. In this

From 4280bcc98b1896f681c370f25e4adb1b9b648533 Mon Sep 17 00:00:00 2001
From: Jakob Vogel 
Date: Tue, 1 Aug 2023 14:33:05 +0200
Subject: [PATCH 020/111] =?UTF-8?q?Extracts=20base=20class=20for=20tries?=
 =?UTF-8?q?=20=F0=9F=93=A6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../java/sirius/kernel/commons/BaseTrie.java  | 413 ++++++++++++++++++
 src/main/java/sirius/kernel/commons/Trie.java | 373 +---------------
 .../kotlin/sirius/kernel/commons/TrieTest.kt  |   4 +-
 3 files changed, 426 insertions(+), 364 deletions(-)
 create mode 100644 src/main/java/sirius/kernel/commons/BaseTrie.java

diff --git a/src/main/java/sirius/kernel/commons/BaseTrie.java b/src/main/java/sirius/kernel/commons/BaseTrie.java
new file mode 100644
index 00000000..91e8dd0e
--- /dev/null
+++ b/src/main/java/sirius/kernel/commons/BaseTrie.java
@@ -0,0 +1,413 @@
+/*
+ * Made with all the love in the world
+ * by scireum in Remshalden, Germany
+ *
+ * Copyright by scireum GmbH
+ * http://www.scireum.de - info@scireum.de
+ */
+
+package sirius.kernel.commons;
+
+import javax.annotation.Nonnull;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * A map like data structure which associates strings (char sequences) to values.
+ * 

+ * A trie is a highly efficient data structure for iterating through a string and retrieving a previously stored + * value. Checking containment or retrieving a value has guaranteed O(n) runtime, where n is the length of the + * processed string, independent of the size of the trie. + *

+ * An Example: If we have a list of stop words: "one", "two", "three" and want to detect if these occur in a + * given text, we can do the following: + *

+ * {@code
+ * Trie trie = Trie.create();
+ *
+ * trie.put("one", true);
+ * trie.put("two", true);
+ * trie.put("three", true);
+ *
+ * String check = "I'd like to have three beer please";
+ * Trie.ContainmentIterator iter = trie.iterator();
+ *
+ * for (int i = 0; i < check.length(); i++) {
+ *     if (!iter.doContinue(check.charAt(i))) {
+ *         if (iter.isCompleted()) {
+ *             System.out.println("Found!");
+ *         }
+ *         iter.resetWith(check.charAt(i));
+ *     }
+ * }
+ * if (iter.isCompleted()) {
+ *     System.out.println("Found!");
+ * }
+ *
+ * }
+ * 
+ * + * @param the type of values managed by the trie + */ +public abstract class BaseTrie { + + /** + * Contains the root of the trie. + */ + protected final Node root = new Node(); + + /** + * Represents an iterator which navigates through the trie character by character. + * + * @param the type of values managed by the trie + */ + public interface ContainmentIterator { + + /** + * Determines if the current path can be continued with the given character. + *

+ * This will not change the internal state. + * + * @param c the character to continue with + * @return true if the current path can be continued using the given character, + * false otherwise. + */ + boolean canContinue(int c); + + /** + * Tries to continue the current path with the given character. + *

+ * If the current path can be continued, the internal state will be updated. Otherwise, the internal + * state will remain unchanged - the iterator is not reset automatically. + * + * @param c the character to continue with + * @return true if it was possible to continue using the given character, false otherwise + */ + boolean doContinue(int c); + + /** + * Returns the value associated with the key represented by the path traversed so far. + * + * @return the value represented by the path traversed to far or null if no value is available + */ + V getValue(); + + /** + * Sets the value to be associated with the key represented by the path traversed so far. + * + * @param value the value to set + */ + void setValue(V value); + + /** + * Determines if the iterator is currently pointing at a valid match. + * + * @return true if a value was previously associated with path traversed so far, + * false otherwise + */ + boolean isCompleted(); + + /** + * Determines if the iterator can backtrack. + * + * @return true if at least one transition took place, flfalse if the iterator is at the + * root node. + */ + boolean canGoBack(); + + /** + * Undoes the latest transition to support backtracking. + */ + void goBack(); + + /** + * Returns a set of all possible continuations for the current state of the iterator. + * + * @return a set of all possible characters to continue the current path + */ + Set getPossibilities(); + + /** + * Restarts the iterator at the beginning of the trie. + */ + void reset(); + + /** + * Restarts the iterator at the beginning and tries to perform the next transition using the given character. + * + * @param c the character to try to use after resetting the iterator + * @return true if the transition using c was possible, false otherwise. In this + * case the iterator remains in the "reset" state and can be used as if reset() was called. + */ + boolean resetWith(int c); + } + + /** + * Internal class representing a single node in the trie. + */ + protected class Node { + + /** + * Points to the parent node of this node. + */ + protected Node parent; + + /** + * Contains a sorted list of keys. + */ + protected final List keys = new ArrayList<>(); + + /** + * Contains the list of continuations matching the keys list. + */ + protected final List continuations = new ArrayList<>(); + + /** + * Contains the value associated with the path to this node. + */ + protected V value; + } + + /** + * Internal implementation of the ContainmentIterator. + */ + protected class ContainmentIteratorImpl implements ContainmentIterator { + + protected Node current = root; + + @Override + public boolean canContinue(int c) { + int index = Collections.binarySearch(current.keys, c); + return !(index < 0 || current.keys.get(index) != c); + } + + @Override + public boolean doContinue(int c) { + int index = Collections.binarySearch(current.keys, c); + if (index < 0 || current.keys.get(index) != c) { + return false; + } + current = current.continuations.get(index); + return true; + } + + /** + * Adds a new step for the given character. Internally, a binary search is performed as the key-list + * is sorted ascending. + */ + void addStep(int c) { + int index = Collections.binarySearch(current.keys, c); + if (index < 0) { + index = (index + 1) * -1; + current.keys.add(index, c); + Node newNode = new Node(); + newNode.parent = current; + current.continuations.add(index, newNode); + current = newNode; + } else { + current = current.continuations.get(index); + } + } + + @Override + public V getValue() { + return current.value; + } + + @Override + public void setValue(V value) { + current.value = value; + } + + @Override + public boolean isCompleted() { + return current.value != null; + } + + @Override + public boolean canGoBack() { + return current != root; + } + + @Override + public void goBack() { + current = current.parent; + } + + @Override + public Set getPossibilities() { + return new TreeSet<>(current.keys); + } + + @Override + public void reset() { + current = root; + } + + @Override + public boolean resetWith(int c) { + current = root; + return doContinue(c); + } + } + + /** + * Determines if the given key is contained in the trie. + * + * @param key the key to check for. + * @return true if a value is associated with the path represented by the given key, + * false otherwise + */ + public boolean containsKey(@Nonnull CharSequence key) { + if (Strings.isEmpty(key)) { + throw new IllegalArgumentException("key"); + } + + ContainmentIterator iterator = iterator(); + int[] sequence = stream(key).toArray(); + for (int index = 0; index < sequence.length; index++) { + if (!iterator.doContinue(sequence[index])) { + return false; + } + } + return iterator.isCompleted(); + } + + /** + * Generates a new iterator for the underlying trie. + * + * @return a new iterator to navigate through the underlying trie + */ + public ContainmentIterator iterator() { + return new ContainmentIteratorImpl(); + } + + /** + * Returns the value associated with the given key. + * + * @param key the path to navigate through + * @return the value associated with the path defined by the given key or null if no value is present + */ + public V get(@Nonnull CharSequence key) { + if (Strings.isEmpty(key)) { + throw new IllegalArgumentException("key"); + } + + ContainmentIterator iterator = iterator(); + int[] sequence = stream(key).toArray(); + for (int index = 0; index < sequence.length; index++) { + if (!iterator.doContinue(sequence[index])) { + return null; + } + } + return iterator.getValue(); + } + + /** + * Associates the given key with the given value. + * + * @param key the path to store the given value + * @param value the value to store in the trie. + */ + public final void put(@Nonnull CharSequence key, V value) { + if (Strings.isEmpty(key)) { + throw new IllegalArgumentException("key"); + } + if (value == null) { + throw new IllegalArgumentException("value"); + } + + ContainmentIterator iterator = iterator(); + int[] sequence = stream(key).toArray(); + for (int index = 0; index < sequence.length; index++) { + if (!iterator.doContinue(sequence[index])) { + ((ContainmentIteratorImpl) iterator).addStep(sequence[index]); + } + } + iterator.setValue(value); + } + + /** + * Retrieves all keys that are stored in this {@link BaseTrie}. + * + * @return an {@link Collections#unmodifiableSet(Set) unmodifiable set} of all keys that are stored in this {@link BaseTrie} + */ + public Set keySet() { + return getAllKeysBeginningWith(""); + } + + /** + * Retrieves the number of keys that are stored in this {@link BaseTrie}. + * + * @return the size of this {@link BaseTrie}'s {@link #keySet() key set} + */ + public int size() { + return keySet().size(); + } + + /** + * Performs a prefix search within this {@link BaseTrie}'s {@link #keySet() key set} + * + * @param prefix to search for + * @return an {@link Collections#unmodifiableSet(Set) unmodifiable set} holding all keys that are beginning with + * the given prefix (may include prefix itself) + */ + public Set getAllKeysBeginningWith(CharSequence prefix) { + if (Strings.isEmpty(prefix)) { + prefix = ""; + } + + ContainmentIterator iterator = iterator(); + int[] sequence = stream(prefix).toArray(); + for (int index = 0; index < sequence.length; index++) { + if (!iterator.doContinue(sequence[index])) { + return Collections.emptySet(); + } + } + + return getAllKeysBeginningWith(stream(prefix).boxed().collect(Collectors.toCollection(LinkedList::new)), + iterator); + } + + private Set getAllKeysBeginningWith(LinkedList prefix, ContainmentIterator iter) { + if (iter.getPossibilities().isEmpty()) { + if (iter.getValue() != null) { + return Collections.singleton(assembleString(prefix)); + } else { + return Collections.emptySet(); + } + } + + Set result = new HashSet<>(); + if (iter.getValue() != null) { + result.add(assembleString(prefix)); + } + for (Integer possibility : iter.getPossibilities()) { + iter.doContinue(possibility); + prefix.addLast(possibility); + result.addAll(getAllKeysBeginningWith(prefix, iter)); + prefix.removeLast(); + iter.goBack(); + } + + return Collections.unmodifiableSet(result); + } + + /** + * Streams the given string into a sequence of "bits" that make up the keys of the trie. The nature of these bits + * depends on the implementation. + * + * @param string the string to stream + * @return a stream of "bits" that make up the keys of the trie + */ + protected abstract IntStream stream(CharSequence string); + + protected abstract String assembleString(List keys); +} diff --git a/src/main/java/sirius/kernel/commons/Trie.java b/src/main/java/sirius/kernel/commons/Trie.java index adab7063..a3ff5499 100644 --- a/src/main/java/sirius/kernel/commons/Trie.java +++ b/src/main/java/sirius/kernel/commons/Trie.java @@ -8,55 +8,15 @@ package sirius.kernel.commons; -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Set; -import java.util.TreeSet; +import java.util.stream.IntStream; /** - * A map like data structure which associates strings (char sequences) to values. - *

- * A trie is a highly efficient data structure for iterating through a string and retrieving a previously stored - * value. Checking containment or retrieving a value has guaranteed O(n) runtime, where n is the length of the - * processed string, independent of the size of the trie. - *

- * An Example: If we have a list of stop words: "one", "two", "three" and want to detect if these occur in a - * given text, we can do the following: - *

- * {@code
- * Trie trie = Trie.create();
- *
- * trie.put("one", true);
- * trie.put("two", true);
- * trie.put("three", true);
- *
- * String check = "I'd like to have three beer please";
- * Trie.ContainmentIterator iter = trie.iterator();
- *
- * for(int i = 0; i < check.length(); i++) {
- *     if (!iter.doContinue(check.charAt(i))) {
- *         if (iter.isCompleted()) {
- *             System.out.println("Found!");
- *         } else {
- *             iter.resetWith(check.charAt(i));
- *         }
- *     }
- * }
- *
- * }
- * 
+ * Implements the {@link BaseTrie} to use {@link Character}-based keys. * * @param the type of values managed by the trie */ -public class Trie { - - /** - * Contains the root of the Trie - */ - private final Node root = new Node(); +public class Trie extends BaseTrie { /** * Creates a new {@link Trie} without forcing you to re-type the generics. @@ -68,326 +28,15 @@ public static Trie create() { return new Trie<>(); } - /** - * Represents an iterator which navigates through the trie character by character. - * - * @param the type of values managed by the trie - */ - public interface ContainmentIterator { - - /** - * Determines if the current path can be continued with the given character. - *

- * This will not change the internal state. - * - * @param c the character to continue with - * @return true if the current path can be continued using the given character, - * false otherwise. - */ - boolean canContinue(char c); - - /** - * Tries to continue the current path with the given character. - *

- * If the current path can be continued, the internal state will be updated. Otherwise, the internal - * state will remain unchanged - the iterator is not reset automatically. - * - * @param c the character to continue with - * @return true if it was possible to continue using the given character, false otherwise - */ - boolean doContinue(char c); - - /** - * Returns the value associated with the key represented by the path traversed so far. - * - * @return the value represented by the path traversed to far or null if no value is available - */ - V getValue(); - - /** - * Sets the value to be associated with the key represented by the path traversed so far - * - * @param value the value to set - */ - void setValue(V value); - - /** - * Determines if the iterator is currently pointing at a valid match. - * - * @return true if a value was previously associated with path traversed so far, - * false otherwise - */ - boolean isCompleted(); - - /** - * Determines if the iterator can backtrack. - * - * @return true if at least one transition took place, flfalse if the iterator is at the - * root node. - */ - boolean canGoBack(); - - /** - * Undoes the latest transition to support backtracking - */ - void goBack(); - - /** - * Returns a set of all possible continuations for the current state of the iterator - * - * @return a set of all possible characters to continue the current path - */ - Set getPossibilities(); - - /** - * Restarts the iterator at the beginning of the trie. - */ - void reset(); - - /** - * Restarts the iterator at the beginning and tries to perform the next transition using the given character. - * - * @param c the character to try to use after resetting the iterator - * @return true if the transition using c was possible, false otherwise. In this - * case the iterator remains in the "reset" state and can be used as if reset() was called. - */ - boolean resetWith(char c); + @Override + protected IntStream stream(CharSequence string) { + return string.chars(); } - /** - * Internal class representing a single node in the trie - */ - class Node { - /** - * Points to the parent node of this node - */ - private Node parent; - - /** - * Contains a sorted list of keys - */ - private final List keys = new ArrayList<>(); - - /** - * Contains the list of continuations matching the keys list - */ - private final List continuations = new ArrayList<>(); - - /** - * Contains the value associated with the path to this node - */ - private V value; - } - - /** - * Internal implementation of the ContainmentIterator - */ - private class ContainmentIteratorImpl implements ContainmentIterator { - - private Node current = root; - - @Override - public boolean canContinue(char c) { - int index = Collections.binarySearch(current.keys, c); - return !(index < 0 || current.keys.get(index) != c); - } - - @Override - public boolean doContinue(char c) { - int index = Collections.binarySearch(current.keys, c); - if (index < 0 || current.keys.get(index) != c) { - return false; - } - current = current.continuations.get(index); - return true; - } - - /** - * Adds a new step for the given character. Internally, a binary search is performed as the key-list - * is sorted ascending. - */ - void addStep(char c) { - int index = Collections.binarySearch(current.keys, c); - if (index < 0) { - index = (index + 1) * -1; - current.keys.add(index, c); - Node newNode = new Node(); - newNode.parent = current; - current.continuations.add(index, newNode); - current = newNode; - } else { - current = current.continuations.get(index); - } - } - - @Override - public V getValue() { - return current.value; - } - - @Override - public void setValue(V value) { - current.value = value; - } - - @Override - public boolean isCompleted() { - return current.value != null; - } - - @Override - public boolean canGoBack() { - return current != root; - } - - @Override - public void goBack() { - current = current.parent; - } - - @Override - public Set getPossibilities() { - return new TreeSet<>(current.keys); - } - - @Override - public void reset() { - current = root; - } - - @Override - public boolean resetWith(char c) { - current = root; - return doContinue(c); - } - } - - /** - * Determines if the given key is contained in the trie. - * - * @param key the key to check for. - * @return true if a value is associated with the path represented by the given key, - * false otherwise - */ - public boolean containsKey(@Nonnull CharSequence key) { - if (Strings.isEmpty(key)) { - throw new IllegalArgumentException("key"); - } - ContainmentIterator iter = iterator(); - for (int i = 0; i < key.length(); i++) { - if (!iter.doContinue(key.charAt(i))) { - return false; - } - } - return iter.isCompleted(); - } - - /** - * Generates a new iterator for the underlying trie. - * - * @return a new iterator to navigate through the underlying trie - */ - public ContainmentIterator iterator() { - return new ContainmentIteratorImpl(); - } - - /** - * Returns the value associated with the given key. - * - * @param key the path to navigate through - * @return the value associated with the path defined by the given key or null if no value is present - */ - public V get(@Nonnull CharSequence key) { - if (Strings.isEmpty(key)) { - throw new IllegalArgumentException("key"); - } - ContainmentIterator iter = iterator(); - for (int i = 0; i < key.length(); i++) { - if (!iter.doContinue(key.charAt(i))) { - return null; - } - } - return iter.getValue(); - } - - /** - * Associates the given key with the given value. - * - * @param key the path to store the given value - * @param value the value to store in the trie. - */ - @SuppressWarnings("squid:S2583") - @Explain("Duplicate @Nonnull value check as it isn't enforced by the compiler.") - public void put(@Nonnull CharSequence key, @Nonnull V value) { - if (Strings.isEmpty(key)) { - throw new IllegalArgumentException("key"); - } - if (value == null) { - throw new IllegalArgumentException("value"); - } - ContainmentIterator iter = iterator(); - for (int i = 0; i < key.length(); i++) { - if (!iter.doContinue(key.charAt(i))) { - ((ContainmentIteratorImpl) iter).addStep(key.charAt(i)); - } - } - iter.setValue(value); - } - - /** - * Retrieves all keys that are stored in this {@link Trie}. - * - * @return an {@link Collections#unmodifiableSet(Set) unmodifiable set} of all keys that are stored in this {@link Trie} - */ - public Set keySet() { - return getAllKeysBeginningWith(""); - } - - /** - * Retrieves the number of keys that are stored in this {@link Trie}. - * - * @return the size of this {@link Trie}'s {@link #keySet() key set} - */ - public int size() { - return keySet().size(); - } - - /** - * Performs a prefix search within this {@link Trie}'s {@link #keySet() key set} - * - * @param prefix to search for - * @return an {@link Collections#unmodifiableSet(Set) unmodifiable set} holding all keys that are beginning with - * the given prefix (may include prefix itself) - */ - public Set getAllKeysBeginningWith(String prefix) { - ContainmentIterator iter = iterator(); - for (int i = 0; i < prefix.length(); i++) { - if (!iter.doContinue(prefix.charAt(i))) { - return Collections.emptySet(); - } - } - return getAllKeysBeginningWith(prefix, iter); - } - - private Set getAllKeysBeginningWith(String prefix, ContainmentIterator iter) { - if (iter.getPossibilities().isEmpty()) { - if (iter.getValue() != null) { - return Collections.singleton(prefix); - } else { - return Collections.emptySet(); - } - } - - Set result = new HashSet<>(); - if (iter.getValue() != null) { - result.add(prefix); - } - for (char possibility : iter.getPossibilities()) { - iter.doContinue(possibility); - result.addAll(getAllKeysBeginningWith(prefix + possibility, iter)); - iter.goBack(); - } - - return Collections.unmodifiableSet(result); + @Override + protected String assembleString(List keys) { + StringBuilder builder = new StringBuilder(); + keys.stream().map(integer -> (char) integer.intValue()).forEach(builder::append); + return builder.toString(); } } diff --git a/src/test/kotlin/sirius/kernel/commons/TrieTest.kt b/src/test/kotlin/sirius/kernel/commons/TrieTest.kt index e6817a6d..b4f1403e 100644 --- a/src/test/kotlin/sirius/kernel/commons/TrieTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/TrieTest.kt @@ -25,11 +25,11 @@ class TrieTest { val iter = trie.iterator() var found = 0 for (index in check.indices) { - if (!iter.doContinue(check[index])) { + if (!iter.doContinue(check[index].code)) { if (iter.isCompleted()) { found = iter.getValue() } - iter.resetWith(check[index]) + iter.resetWith(check[index].code) } } assertEquals(5, found) From c48ae14b022a735198f6e735bf80510b48426afc Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 1 Aug 2023 14:34:10 +0200 Subject: [PATCH 021/111] =?UTF-8?q?Fixes=20test=20case=20=F0=9F=9A=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This bug was not visible in the test runs, as we did not test with a trailing stop word before. --- src/test/kotlin/sirius/kernel/commons/TrieTest.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/kotlin/sirius/kernel/commons/TrieTest.kt b/src/test/kotlin/sirius/kernel/commons/TrieTest.kt index b4f1403e..933b6156 100644 --- a/src/test/kotlin/sirius/kernel/commons/TrieTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/TrieTest.kt @@ -32,6 +32,9 @@ class TrieTest { iter.resetWith(check[index].code) } } + if (iter.isCompleted()) { + found = iter.getValue() + } assertEquals(5, found) assertEquals(2, trie["on"] as Int) assertNull(trie["onx"]) From 4e726ac72a153977b323f002da6e435501af1898 Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 1 Aug 2023 14:34:34 +0200 Subject: [PATCH 022/111] =?UTF-8?q?Introduces=20code-point-based=20trie=20?= =?UTF-8?q?=F0=9F=8C=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sirius/kernel/commons/CodePointTrie.java | 42 ++++++++++ .../kernel/commons/CodePointTrieTest.kt | 78 +++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 src/main/java/sirius/kernel/commons/CodePointTrie.java create mode 100644 src/test/kotlin/sirius/kernel/commons/CodePointTrieTest.kt diff --git a/src/main/java/sirius/kernel/commons/CodePointTrie.java b/src/main/java/sirius/kernel/commons/CodePointTrie.java new file mode 100644 index 00000000..e916db64 --- /dev/null +++ b/src/main/java/sirius/kernel/commons/CodePointTrie.java @@ -0,0 +1,42 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons; + +import java.util.List; +import java.util.stream.IntStream; + +/** + * Implements the {@link BaseTrie} to use keys based on {@link Integer} code points. + * + * @param the type of values managed by the trie + */ +public class CodePointTrie extends BaseTrie { + + /** + * Creates a new {@link CodePointTrie} without forcing you to re-type the generics. + * + * @param the type of values managed by the trie + * @return a new instance of {@link CodePointTrie} + */ + public static CodePointTrie create() { + return new CodePointTrie<>(); + } + + @Override + protected IntStream stream(CharSequence string) { + return string.codePoints(); + } + + @Override + protected String assembleString(List keys) { + StringBuilder builder = new StringBuilder(); + keys.forEach(builder::appendCodePoint); + return builder.toString(); + } +} diff --git a/src/test/kotlin/sirius/kernel/commons/CodePointTrieTest.kt b/src/test/kotlin/sirius/kernel/commons/CodePointTrieTest.kt new file mode 100644 index 00000000..fd6dd9d1 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/CodePointTrieTest.kt @@ -0,0 +1,78 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons + +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue + +/** + * Tests the [CodePointTrie] class. + */ +class CodePointTrieTest { + @Test + fun isFilled() { + val check = "I'd like to have three beer please 👩🏾‍🚀" + val iter = trie.iterator() + var found = 0 + for (codePoint in check.codePoints()) { + if (!iter.doContinue(codePoint)) { + if (iter.isCompleted()) { + found = iter.getValue() + } + iter.resetWith(codePoint) + } + } + if (iter.isCompleted()) { + found = iter.getValue() + } + assertEquals(8, found) + assertEquals(2, trie["on"] as Int) + assertNull(trie["onx"]) + assertTrue { trie.containsKey("thrae") } + assertFalse { trie.containsKey("thre") } + assertTrue { trie.containsKey("👩🏾‍🚀") } + assertFalse { trie.containsKey("👩🏻‍🚀") } + + // the astronaut emoji is a combination of the symbols for woman, skin color, and rocket; we must not find + // parts/prefixes of it + assertFalse { trie.containsKey("👩") } + } + + @Test + fun keySet() { + assertEquals(8, trie.size()) + assertEquals(setOf("one", "on", "one1", "two", "three", "thrae", "th", "👩🏾‍🚀"), trie.keySet()) + assertEquals(setOf("one", "on", "one1", "two", "three", "thrae", "th", "👩🏾‍🚀"), trie.getAllKeysBeginningWith("")) + assertEquals(setOf("one", "on", "one1"), trie.getAllKeysBeginningWith("on")) + assertEquals(setOf("three"), trie.getAllKeysBeginningWith("three")) + assertEquals(0, trie.getAllKeysBeginningWith("threee").size) + } + + companion object { + private lateinit var trie: CodePointTrie + + @JvmStatic + @BeforeAll + fun createTrie() { + trie = CodePointTrie.create() + trie.put("one", 1) + trie.put("on", 2) + trie.put("one1", 3) + trie.put("two", 4) + trie.put("three", 5) + trie.put("thrae", 6) + trie.put("th", 7) + trie.put("👩🏾‍🚀", 8) + } + } +} From 13e3607589eebce845af8696953ed41fa4ae50c9 Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 1 Aug 2023 14:34:49 +0200 Subject: [PATCH 023/111] =?UTF-8?q?Introduces=20helper=20class=20for=20emo?= =?UTF-8?q?jis=20=F0=9F=A4=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/sirius/kernel/commons/Emojis.java | 4838 +++++++++++++++++ .../kotlin/sirius/kernel/commons/EmojiTest.kt | 77 + 2 files changed, 4915 insertions(+) create mode 100644 src/main/java/sirius/kernel/commons/Emojis.java create mode 100644 src/test/kotlin/sirius/kernel/commons/EmojiTest.kt diff --git a/src/main/java/sirius/kernel/commons/Emojis.java b/src/main/java/sirius/kernel/commons/Emojis.java new file mode 100644 index 00000000..e6a1e87a --- /dev/null +++ b/src/main/java/sirius/kernel/commons/Emojis.java @@ -0,0 +1,4838 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons; + +/** + * Provides helper methods to work with emojis. + */ +public class Emojis { + + protected static final CodePointTrie emojiTrie = CodePointTrie.create(); + + static { + emojiTrie.put("😀", true); + emojiTrie.put("😃", true); + emojiTrie.put("😄", true); + emojiTrie.put("😁", true); + emojiTrie.put("😆", true); + emojiTrie.put("😅", true); + emojiTrie.put("🤣", true); + emojiTrie.put("😂", true); + emojiTrie.put("🙂", true); + emojiTrie.put("🙃", true); + emojiTrie.put("🫠", true); + emojiTrie.put("😉", true); + emojiTrie.put("😊", true); + emojiTrie.put("😇", true); + emojiTrie.put("🥰", true); + emojiTrie.put("😍", true); + emojiTrie.put("🤩", true); + emojiTrie.put("😘", true); + emojiTrie.put("😗", true); + emojiTrie.put("☺️", true); + emojiTrie.put("☺", true); + emojiTrie.put("😚", true); + emojiTrie.put("😙", true); + emojiTrie.put("🥲", true); + emojiTrie.put("😋", true); + emojiTrie.put("😛", true); + emojiTrie.put("😜", true); + emojiTrie.put("🤪", true); + emojiTrie.put("😝", true); + emojiTrie.put("🤑", true); + emojiTrie.put("🤗", true); + emojiTrie.put("🤭", true); + emojiTrie.put("🫢", true); + emojiTrie.put("🫣", true); + emojiTrie.put("🤫", true); + emojiTrie.put("🤔", true); + emojiTrie.put("🫡", true); + emojiTrie.put("🤐", true); + emojiTrie.put("🤨", true); + emojiTrie.put("😐", true); + emojiTrie.put("😑", true); + emojiTrie.put("😶", true); + emojiTrie.put("🫥", true); + emojiTrie.put("😶‍🌫️", true); + emojiTrie.put("😶‍🌫", true); + emojiTrie.put("😏", true); + emojiTrie.put("😒", true); + emojiTrie.put("🙄", true); + emojiTrie.put("😬", true); + emojiTrie.put("😮‍💨", true); + emojiTrie.put("🤥", true); + emojiTrie.put("🫨", true); + emojiTrie.put("😌", true); + emojiTrie.put("😔", true); + emojiTrie.put("😪", true); + emojiTrie.put("🤤", true); + emojiTrie.put("😴", true); + emojiTrie.put("😷", true); + emojiTrie.put("🤒", true); + emojiTrie.put("🤕", true); + emojiTrie.put("🤢", true); + emojiTrie.put("🤮", true); + emojiTrie.put("🤧", true); + emojiTrie.put("🥵", true); + emojiTrie.put("🥶", true); + emojiTrie.put("🥴", true); + emojiTrie.put("😵", true); + emojiTrie.put("😵‍💫", true); + emojiTrie.put("🤯", true); + emojiTrie.put("🤠", true); + emojiTrie.put("🥳", true); + emojiTrie.put("🥸", true); + emojiTrie.put("😎", true); + emojiTrie.put("🤓", true); + emojiTrie.put("🧐", true); + emojiTrie.put("😕", true); + emojiTrie.put("🫤", true); + emojiTrie.put("😟", true); + emojiTrie.put("🙁", true); + emojiTrie.put("☹️", true); + emojiTrie.put("☹", true); + emojiTrie.put("😮", true); + emojiTrie.put("😯", true); + emojiTrie.put("😲", true); + emojiTrie.put("😳", true); + emojiTrie.put("🥺", true); + emojiTrie.put("🥹", true); + emojiTrie.put("😦", true); + emojiTrie.put("😧", true); + emojiTrie.put("😨", true); + emojiTrie.put("😰", true); + emojiTrie.put("😥", true); + emojiTrie.put("😢", true); + emojiTrie.put("😭", true); + emojiTrie.put("😱", true); + emojiTrie.put("😖", true); + emojiTrie.put("😣", true); + emojiTrie.put("😞", true); + emojiTrie.put("😓", true); + emojiTrie.put("😩", true); + emojiTrie.put("😫", true); + emojiTrie.put("🥱", true); + emojiTrie.put("😤", true); + emojiTrie.put("😡", true); + emojiTrie.put("😠", true); + emojiTrie.put("🤬", true); + emojiTrie.put("😈", true); + emojiTrie.put("👿", true); + emojiTrie.put("💀", true); + emojiTrie.put("☠️", true); + emojiTrie.put("☠", true); + emojiTrie.put("💩", true); + emojiTrie.put("🤡", true); + emojiTrie.put("👹", true); + emojiTrie.put("👺", true); + emojiTrie.put("👻", true); + emojiTrie.put("👽", true); + emojiTrie.put("👾", true); + emojiTrie.put("🤖", true); + emojiTrie.put("😺", true); + emojiTrie.put("😸", true); + emojiTrie.put("😹", true); + emojiTrie.put("😻", true); + emojiTrie.put("😼", true); + emojiTrie.put("😽", true); + emojiTrie.put("🙀", true); + emojiTrie.put("😿", true); + emojiTrie.put("😾", true); + emojiTrie.put("🙈", true); + emojiTrie.put("🙉", true); + emojiTrie.put("🙊", true); + emojiTrie.put("💌", true); + emojiTrie.put("💘", true); + emojiTrie.put("💝", true); + emojiTrie.put("💖", true); + emojiTrie.put("💗", true); + emojiTrie.put("💓", true); + emojiTrie.put("💞", true); + emojiTrie.put("💕", true); + emojiTrie.put("💟", true); + emojiTrie.put("❣️", true); + emojiTrie.put("❣", true); + emojiTrie.put("💔", true); + emojiTrie.put("❤️‍🔥", true); + emojiTrie.put("❤‍🔥", true); + emojiTrie.put("❤️‍🩹", true); + emojiTrie.put("❤‍🩹", true); + emojiTrie.put("❤️", true); + emojiTrie.put("❤", true); + emojiTrie.put("🩷", true); + emojiTrie.put("🧡", true); + emojiTrie.put("💛", true); + emojiTrie.put("💚", true); + emojiTrie.put("💙", true); + emojiTrie.put("🩵", true); + emojiTrie.put("💜", true); + emojiTrie.put("🤎", true); + emojiTrie.put("🖤", true); + emojiTrie.put("🩶", true); + emojiTrie.put("🤍", true); + emojiTrie.put("💋", true); + emojiTrie.put("💯", true); + emojiTrie.put("💢", true); + emojiTrie.put("💥", true); + emojiTrie.put("💫", true); + emojiTrie.put("💦", true); + emojiTrie.put("💨", true); + emojiTrie.put("🕳️", true); + emojiTrie.put("🕳", true); + emojiTrie.put("💬", true); + emojiTrie.put("👁️‍🗨️", true); + emojiTrie.put("👁‍🗨️", true); + emojiTrie.put("👁️‍🗨", true); + emojiTrie.put("👁‍🗨", true); + emojiTrie.put("🗨️", true); + emojiTrie.put("🗨", true); + emojiTrie.put("🗯️", true); + emojiTrie.put("🗯", true); + emojiTrie.put("💭", true); + emojiTrie.put("💤", true); + emojiTrie.put("👋", true); + emojiTrie.put("👋🏻", true); + emojiTrie.put("👋🏼", true); + emojiTrie.put("👋🏽", true); + emojiTrie.put("👋🏾", true); + emojiTrie.put("👋🏿", true); + emojiTrie.put("🤚", true); + emojiTrie.put("🤚🏻", true); + emojiTrie.put("🤚🏼", true); + emojiTrie.put("🤚🏽", true); + emojiTrie.put("🤚🏾", true); + emojiTrie.put("🤚🏿", true); + emojiTrie.put("🖐️", true); + emojiTrie.put("🖐", true); + emojiTrie.put("🖐🏻", true); + emojiTrie.put("🖐🏼", true); + emojiTrie.put("🖐🏽", true); + emojiTrie.put("🖐🏾", true); + emojiTrie.put("🖐🏿", true); + emojiTrie.put("✋", true); + emojiTrie.put("✋🏻", true); + emojiTrie.put("✋🏼", true); + emojiTrie.put("✋🏽", true); + emojiTrie.put("✋🏾", true); + emojiTrie.put("✋🏿", true); + emojiTrie.put("🖖", true); + emojiTrie.put("🖖🏻", true); + emojiTrie.put("🖖🏼", true); + emojiTrie.put("🖖🏽", true); + emojiTrie.put("🖖🏾", true); + emojiTrie.put("🖖🏿", true); + emojiTrie.put("🫱", true); + emojiTrie.put("🫱🏻", true); + emojiTrie.put("🫱🏼", true); + emojiTrie.put("🫱🏽", true); + emojiTrie.put("🫱🏾", true); + emojiTrie.put("🫱🏿", true); + emojiTrie.put("🫲", true); + emojiTrie.put("🫲🏻", true); + emojiTrie.put("🫲🏼", true); + emojiTrie.put("🫲🏽", true); + emojiTrie.put("🫲🏾", true); + emojiTrie.put("🫲🏿", true); + emojiTrie.put("🫳", true); + emojiTrie.put("🫳🏻", true); + emojiTrie.put("🫳🏼", true); + emojiTrie.put("🫳🏽", true); + emojiTrie.put("🫳🏾", true); + emojiTrie.put("🫳🏿", true); + emojiTrie.put("🫴", true); + emojiTrie.put("🫴🏻", true); + emojiTrie.put("🫴🏼", true); + emojiTrie.put("🫴🏽", true); + emojiTrie.put("🫴🏾", true); + emojiTrie.put("🫴🏿", true); + emojiTrie.put("🫷", true); + emojiTrie.put("🫷🏻", true); + emojiTrie.put("🫷🏼", true); + emojiTrie.put("🫷🏽", true); + emojiTrie.put("🫷🏾", true); + emojiTrie.put("🫷🏿", true); + emojiTrie.put("🫸", true); + emojiTrie.put("🫸🏻", true); + emojiTrie.put("🫸🏼", true); + emojiTrie.put("🫸🏽", true); + emojiTrie.put("🫸🏾", true); + emojiTrie.put("🫸🏿", true); + emojiTrie.put("👌", true); + emojiTrie.put("👌🏻", true); + emojiTrie.put("👌🏼", true); + emojiTrie.put("👌🏽", true); + emojiTrie.put("👌🏾", true); + emojiTrie.put("👌🏿", true); + emojiTrie.put("🤌", true); + emojiTrie.put("🤌🏻", true); + emojiTrie.put("🤌🏼", true); + emojiTrie.put("🤌🏽", true); + emojiTrie.put("🤌🏾", true); + emojiTrie.put("🤌🏿", true); + emojiTrie.put("🤏", true); + emojiTrie.put("🤏🏻", true); + emojiTrie.put("🤏🏼", true); + emojiTrie.put("🤏🏽", true); + emojiTrie.put("🤏🏾", true); + emojiTrie.put("🤏🏿", true); + emojiTrie.put("✌️", true); + emojiTrie.put("✌", true); + emojiTrie.put("✌🏻", true); + emojiTrie.put("✌🏼", true); + emojiTrie.put("✌🏽", true); + emojiTrie.put("✌🏾", true); + emojiTrie.put("✌🏿", true); + emojiTrie.put("🤞", true); + emojiTrie.put("🤞🏻", true); + emojiTrie.put("🤞🏼", true); + emojiTrie.put("🤞🏽", true); + emojiTrie.put("🤞🏾", true); + emojiTrie.put("🤞🏿", true); + emojiTrie.put("🫰", true); + emojiTrie.put("🫰🏻", true); + emojiTrie.put("🫰🏼", true); + emojiTrie.put("🫰🏽", true); + emojiTrie.put("🫰🏾", true); + emojiTrie.put("🫰🏿", true); + emojiTrie.put("🤟", true); + emojiTrie.put("🤟🏻", true); + emojiTrie.put("🤟🏼", true); + emojiTrie.put("🤟🏽", true); + emojiTrie.put("🤟🏾", true); + emojiTrie.put("🤟🏿", true); + emojiTrie.put("🤘", true); + emojiTrie.put("🤘🏻", true); + emojiTrie.put("🤘🏼", true); + emojiTrie.put("🤘🏽", true); + emojiTrie.put("🤘🏾", true); + emojiTrie.put("🤘🏿", true); + emojiTrie.put("🤙", true); + emojiTrie.put("🤙🏻", true); + emojiTrie.put("🤙🏼", true); + emojiTrie.put("🤙🏽", true); + emojiTrie.put("🤙🏾", true); + emojiTrie.put("🤙🏿", true); + emojiTrie.put("👈", true); + emojiTrie.put("👈🏻", true); + emojiTrie.put("👈🏼", true); + emojiTrie.put("👈🏽", true); + emojiTrie.put("👈🏾", true); + emojiTrie.put("👈🏿", true); + emojiTrie.put("👉", true); + emojiTrie.put("👉🏻", true); + emojiTrie.put("👉🏼", true); + emojiTrie.put("👉🏽", true); + emojiTrie.put("👉🏾", true); + emojiTrie.put("👉🏿", true); + emojiTrie.put("👆", true); + emojiTrie.put("👆🏻", true); + emojiTrie.put("👆🏼", true); + emojiTrie.put("👆🏽", true); + emojiTrie.put("👆🏾", true); + emojiTrie.put("👆🏿", true); + emojiTrie.put("🖕", true); + emojiTrie.put("🖕🏻", true); + emojiTrie.put("🖕🏼", true); + emojiTrie.put("🖕🏽", true); + emojiTrie.put("🖕🏾", true); + emojiTrie.put("🖕🏿", true); + emojiTrie.put("👇", true); + emojiTrie.put("👇🏻", true); + emojiTrie.put("👇🏼", true); + emojiTrie.put("👇🏽", true); + emojiTrie.put("👇🏾", true); + emojiTrie.put("👇🏿", true); + emojiTrie.put("☝️", true); + emojiTrie.put("☝", true); + emojiTrie.put("☝🏻", true); + emojiTrie.put("☝🏼", true); + emojiTrie.put("☝🏽", true); + emojiTrie.put("☝🏾", true); + emojiTrie.put("☝🏿", true); + emojiTrie.put("🫵", true); + emojiTrie.put("🫵🏻", true); + emojiTrie.put("🫵🏼", true); + emojiTrie.put("🫵🏽", true); + emojiTrie.put("🫵🏾", true); + emojiTrie.put("🫵🏿", true); + emojiTrie.put("👍", true); + emojiTrie.put("👍🏻", true); + emojiTrie.put("👍🏼", true); + emojiTrie.put("👍🏽", true); + emojiTrie.put("👍🏾", true); + emojiTrie.put("👍🏿", true); + emojiTrie.put("👎", true); + emojiTrie.put("👎🏻", true); + emojiTrie.put("👎🏼", true); + emojiTrie.put("👎🏽", true); + emojiTrie.put("👎🏾", true); + emojiTrie.put("👎🏿", true); + emojiTrie.put("✊", true); + emojiTrie.put("✊🏻", true); + emojiTrie.put("✊🏼", true); + emojiTrie.put("✊🏽", true); + emojiTrie.put("✊🏾", true); + emojiTrie.put("✊🏿", true); + emojiTrie.put("👊", true); + emojiTrie.put("👊🏻", true); + emojiTrie.put("👊🏼", true); + emojiTrie.put("👊🏽", true); + emojiTrie.put("👊🏾", true); + emojiTrie.put("👊🏿", true); + emojiTrie.put("🤛", true); + emojiTrie.put("🤛🏻", true); + emojiTrie.put("🤛🏼", true); + emojiTrie.put("🤛🏽", true); + emojiTrie.put("🤛🏾", true); + emojiTrie.put("🤛🏿", true); + emojiTrie.put("🤜", true); + emojiTrie.put("🤜🏻", true); + emojiTrie.put("🤜🏼", true); + emojiTrie.put("🤜🏽", true); + emojiTrie.put("🤜🏾", true); + emojiTrie.put("🤜🏿", true); + emojiTrie.put("👏", true); + emojiTrie.put("👏🏻", true); + emojiTrie.put("👏🏼", true); + emojiTrie.put("👏🏽", true); + emojiTrie.put("👏🏾", true); + emojiTrie.put("👏🏿", true); + emojiTrie.put("🙌", true); + emojiTrie.put("🙌🏻", true); + emojiTrie.put("🙌🏼", true); + emojiTrie.put("🙌🏽", true); + emojiTrie.put("🙌🏾", true); + emojiTrie.put("🙌🏿", true); + emojiTrie.put("🫶", true); + emojiTrie.put("🫶🏻", true); + emojiTrie.put("🫶🏼", true); + emojiTrie.put("🫶🏽", true); + emojiTrie.put("🫶🏾", true); + emojiTrie.put("🫶🏿", true); + emojiTrie.put("👐", true); + emojiTrie.put("👐🏻", true); + emojiTrie.put("👐🏼", true); + emojiTrie.put("👐🏽", true); + emojiTrie.put("👐🏾", true); + emojiTrie.put("👐🏿", true); + emojiTrie.put("🤲", true); + emojiTrie.put("🤲🏻", true); + emojiTrie.put("🤲🏼", true); + emojiTrie.put("🤲🏽", true); + emojiTrie.put("🤲🏾", true); + emojiTrie.put("🤲🏿", true); + emojiTrie.put("🤝", true); + emojiTrie.put("🤝🏻", true); + emojiTrie.put("🤝🏼", true); + emojiTrie.put("🤝🏽", true); + emojiTrie.put("🤝🏾", true); + emojiTrie.put("🤝🏿", true); + emojiTrie.put("🫱🏻‍🫲🏼", true); + emojiTrie.put("🫱🏻‍🫲🏽", true); + emojiTrie.put("🫱🏻‍🫲🏾", true); + emojiTrie.put("🫱🏻‍🫲🏿", true); + emojiTrie.put("🫱🏼‍🫲🏻", true); + emojiTrie.put("🫱🏼‍🫲🏽", true); + emojiTrie.put("🫱🏼‍🫲🏾", true); + emojiTrie.put("🫱🏼‍🫲🏿", true); + emojiTrie.put("🫱🏽‍🫲🏻", true); + emojiTrie.put("🫱🏽‍🫲🏼", true); + emojiTrie.put("🫱🏽‍🫲🏾", true); + emojiTrie.put("🫱🏽‍🫲🏿", true); + emojiTrie.put("🫱🏾‍🫲🏻", true); + emojiTrie.put("🫱🏾‍🫲🏼", true); + emojiTrie.put("🫱🏾‍🫲🏽", true); + emojiTrie.put("🫱🏾‍🫲🏿", true); + emojiTrie.put("🫱🏿‍🫲🏻", true); + emojiTrie.put("🫱🏿‍🫲🏼", true); + emojiTrie.put("🫱🏿‍🫲🏽", true); + emojiTrie.put("🫱🏿‍🫲🏾", true); + emojiTrie.put("🙏", true); + emojiTrie.put("🙏🏻", true); + emojiTrie.put("🙏🏼", true); + emojiTrie.put("🙏🏽", true); + emojiTrie.put("🙏🏾", true); + emojiTrie.put("🙏🏿", true); + emojiTrie.put("✍️", true); + emojiTrie.put("✍", true); + emojiTrie.put("✍🏻", true); + emojiTrie.put("✍🏼", true); + emojiTrie.put("✍🏽", true); + emojiTrie.put("✍🏾", true); + emojiTrie.put("✍🏿", true); + emojiTrie.put("💅", true); + emojiTrie.put("💅🏻", true); + emojiTrie.put("💅🏼", true); + emojiTrie.put("💅🏽", true); + emojiTrie.put("💅🏾", true); + emojiTrie.put("💅🏿", true); + emojiTrie.put("🤳", true); + emojiTrie.put("🤳🏻", true); + emojiTrie.put("🤳🏼", true); + emojiTrie.put("🤳🏽", true); + emojiTrie.put("🤳🏾", true); + emojiTrie.put("🤳🏿", true); + emojiTrie.put("💪", true); + emojiTrie.put("💪🏻", true); + emojiTrie.put("💪🏼", true); + emojiTrie.put("💪🏽", true); + emojiTrie.put("💪🏾", true); + emojiTrie.put("💪🏿", true); + emojiTrie.put("🦾", true); + emojiTrie.put("🦿", true); + emojiTrie.put("🦵", true); + emojiTrie.put("🦵🏻", true); + emojiTrie.put("🦵🏼", true); + emojiTrie.put("🦵🏽", true); + emojiTrie.put("🦵🏾", true); + emojiTrie.put("🦵🏿", true); + emojiTrie.put("🦶", true); + emojiTrie.put("🦶🏻", true); + emojiTrie.put("🦶🏼", true); + emojiTrie.put("🦶🏽", true); + emojiTrie.put("🦶🏾", true); + emojiTrie.put("🦶🏿", true); + emojiTrie.put("👂", true); + emojiTrie.put("👂🏻", true); + emojiTrie.put("👂🏼", true); + emojiTrie.put("👂🏽", true); + emojiTrie.put("👂🏾", true); + emojiTrie.put("👂🏿", true); + emojiTrie.put("🦻", true); + emojiTrie.put("🦻🏻", true); + emojiTrie.put("🦻🏼", true); + emojiTrie.put("🦻🏽", true); + emojiTrie.put("🦻🏾", true); + emojiTrie.put("🦻🏿", true); + emojiTrie.put("👃", true); + emojiTrie.put("👃🏻", true); + emojiTrie.put("👃🏼", true); + emojiTrie.put("👃🏽", true); + emojiTrie.put("👃🏾", true); + emojiTrie.put("👃🏿", true); + emojiTrie.put("🧠", true); + emojiTrie.put("🫀", true); + emojiTrie.put("🫁", true); + emojiTrie.put("🦷", true); + emojiTrie.put("🦴", true); + emojiTrie.put("👀", true); + emojiTrie.put("👁️", true); + emojiTrie.put("👁", true); + emojiTrie.put("👅", true); + emojiTrie.put("👄", true); + emojiTrie.put("🫦", true); + emojiTrie.put("👶", true); + emojiTrie.put("👶🏻", true); + emojiTrie.put("👶🏼", true); + emojiTrie.put("👶🏽", true); + emojiTrie.put("👶🏾", true); + emojiTrie.put("👶🏿", true); + emojiTrie.put("🧒", true); + emojiTrie.put("🧒🏻", true); + emojiTrie.put("🧒🏼", true); + emojiTrie.put("🧒🏽", true); + emojiTrie.put("🧒🏾", true); + emojiTrie.put("🧒🏿", true); + emojiTrie.put("👦", true); + emojiTrie.put("👦🏻", true); + emojiTrie.put("👦🏼", true); + emojiTrie.put("👦🏽", true); + emojiTrie.put("👦🏾", true); + emojiTrie.put("👦🏿", true); + emojiTrie.put("👧", true); + emojiTrie.put("👧🏻", true); + emojiTrie.put("👧🏼", true); + emojiTrie.put("👧🏽", true); + emojiTrie.put("👧🏾", true); + emojiTrie.put("👧🏿", true); + emojiTrie.put("🧑", true); + emojiTrie.put("🧑🏻", true); + emojiTrie.put("🧑🏼", true); + emojiTrie.put("🧑🏽", true); + emojiTrie.put("🧑🏾", true); + emojiTrie.put("🧑🏿", true); + emojiTrie.put("👱", true); + emojiTrie.put("👱🏻", true); + emojiTrie.put("👱🏼", true); + emojiTrie.put("👱🏽", true); + emojiTrie.put("👱🏾", true); + emojiTrie.put("👱🏿", true); + emojiTrie.put("👨", true); + emojiTrie.put("👨🏻", true); + emojiTrie.put("👨🏼", true); + emojiTrie.put("👨🏽", true); + emojiTrie.put("👨🏾", true); + emojiTrie.put("👨🏿", true); + emojiTrie.put("🧔", true); + emojiTrie.put("🧔🏻", true); + emojiTrie.put("🧔🏼", true); + emojiTrie.put("🧔🏽", true); + emojiTrie.put("🧔🏾", true); + emojiTrie.put("🧔🏿", true); + emojiTrie.put("🧔‍♂️", true); + emojiTrie.put("🧔‍♂", true); + emojiTrie.put("🧔🏻‍♂️", true); + emojiTrie.put("🧔🏻‍♂", true); + emojiTrie.put("🧔🏼‍♂️", true); + emojiTrie.put("🧔🏼‍♂", true); + emojiTrie.put("🧔🏽‍♂️", true); + emojiTrie.put("🧔🏽‍♂", true); + emojiTrie.put("🧔🏾‍♂️", true); + emojiTrie.put("🧔🏾‍♂", true); + emojiTrie.put("🧔🏿‍♂️", true); + emojiTrie.put("🧔🏿‍♂", true); + emojiTrie.put("🧔‍♀️", true); + emojiTrie.put("🧔‍♀", true); + emojiTrie.put("🧔🏻‍♀️", true); + emojiTrie.put("🧔🏻‍♀", true); + emojiTrie.put("🧔🏼‍♀️", true); + emojiTrie.put("🧔🏼‍♀", true); + emojiTrie.put("🧔🏽‍♀️", true); + emojiTrie.put("🧔🏽‍♀", true); + emojiTrie.put("🧔🏾‍♀️", true); + emojiTrie.put("🧔🏾‍♀", true); + emojiTrie.put("🧔🏿‍♀️", true); + emojiTrie.put("🧔🏿‍♀", true); + emojiTrie.put("👨‍🦰", true); + emojiTrie.put("👨🏻‍🦰", true); + emojiTrie.put("👨🏼‍🦰", true); + emojiTrie.put("👨🏽‍🦰", true); + emojiTrie.put("👨🏾‍🦰", true); + emojiTrie.put("👨🏿‍🦰", true); + emojiTrie.put("👨‍🦱", true); + emojiTrie.put("👨🏻‍🦱", true); + emojiTrie.put("👨🏼‍🦱", true); + emojiTrie.put("👨🏽‍🦱", true); + emojiTrie.put("👨🏾‍🦱", true); + emojiTrie.put("👨🏿‍🦱", true); + emojiTrie.put("👨‍🦳", true); + emojiTrie.put("👨🏻‍🦳", true); + emojiTrie.put("👨🏼‍🦳", true); + emojiTrie.put("👨🏽‍🦳", true); + emojiTrie.put("👨🏾‍🦳", true); + emojiTrie.put("👨🏿‍🦳", true); + emojiTrie.put("👨‍🦲", true); + emojiTrie.put("👨🏻‍🦲", true); + emojiTrie.put("👨🏼‍🦲", true); + emojiTrie.put("👨🏽‍🦲", true); + emojiTrie.put("👨🏾‍🦲", true); + emojiTrie.put("👨🏿‍🦲", true); + emojiTrie.put("👩", true); + emojiTrie.put("👩🏻", true); + emojiTrie.put("👩🏼", true); + emojiTrie.put("👩🏽", true); + emojiTrie.put("👩🏾", true); + emojiTrie.put("👩🏿", true); + emojiTrie.put("👩‍🦰", true); + emojiTrie.put("👩🏻‍🦰", true); + emojiTrie.put("👩🏼‍🦰", true); + emojiTrie.put("👩🏽‍🦰", true); + emojiTrie.put("👩🏾‍🦰", true); + emojiTrie.put("👩🏿‍🦰", true); + emojiTrie.put("🧑‍🦰", true); + emojiTrie.put("🧑🏻‍🦰", true); + emojiTrie.put("🧑🏼‍🦰", true); + emojiTrie.put("🧑🏽‍🦰", true); + emojiTrie.put("🧑🏾‍🦰", true); + emojiTrie.put("🧑🏿‍🦰", true); + emojiTrie.put("👩‍🦱", true); + emojiTrie.put("👩🏻‍🦱", true); + emojiTrie.put("👩🏼‍🦱", true); + emojiTrie.put("👩🏽‍🦱", true); + emojiTrie.put("👩🏾‍🦱", true); + emojiTrie.put("👩🏿‍🦱", true); + emojiTrie.put("🧑‍🦱", true); + emojiTrie.put("🧑🏻‍🦱", true); + emojiTrie.put("🧑🏼‍🦱", true); + emojiTrie.put("🧑🏽‍🦱", true); + emojiTrie.put("🧑🏾‍🦱", true); + emojiTrie.put("🧑🏿‍🦱", true); + emojiTrie.put("👩‍🦳", true); + emojiTrie.put("👩🏻‍🦳", true); + emojiTrie.put("👩🏼‍🦳", true); + emojiTrie.put("👩🏽‍🦳", true); + emojiTrie.put("👩🏾‍🦳", true); + emojiTrie.put("👩🏿‍🦳", true); + emojiTrie.put("🧑‍🦳", true); + emojiTrie.put("🧑🏻‍🦳", true); + emojiTrie.put("🧑🏼‍🦳", true); + emojiTrie.put("🧑🏽‍🦳", true); + emojiTrie.put("🧑🏾‍🦳", true); + emojiTrie.put("🧑🏿‍🦳", true); + emojiTrie.put("👩‍🦲", true); + emojiTrie.put("👩🏻‍🦲", true); + emojiTrie.put("👩🏼‍🦲", true); + emojiTrie.put("👩🏽‍🦲", true); + emojiTrie.put("👩🏾‍🦲", true); + emojiTrie.put("👩🏿‍🦲", true); + emojiTrie.put("🧑‍🦲", true); + emojiTrie.put("🧑🏻‍🦲", true); + emojiTrie.put("🧑🏼‍🦲", true); + emojiTrie.put("🧑🏽‍🦲", true); + emojiTrie.put("🧑🏾‍🦲", true); + emojiTrie.put("🧑🏿‍🦲", true); + emojiTrie.put("👱‍♀️", true); + emojiTrie.put("👱‍♀", true); + emojiTrie.put("👱🏻‍♀️", true); + emojiTrie.put("👱🏻‍♀", true); + emojiTrie.put("👱🏼‍♀️", true); + emojiTrie.put("👱🏼‍♀", true); + emojiTrie.put("👱🏽‍♀️", true); + emojiTrie.put("👱🏽‍♀", true); + emojiTrie.put("👱🏾‍♀️", true); + emojiTrie.put("👱🏾‍♀", true); + emojiTrie.put("👱🏿‍♀️", true); + emojiTrie.put("👱🏿‍♀", true); + emojiTrie.put("👱‍♂️", true); + emojiTrie.put("👱‍♂", true); + emojiTrie.put("👱🏻‍♂️", true); + emojiTrie.put("👱🏻‍♂", true); + emojiTrie.put("👱🏼‍♂️", true); + emojiTrie.put("👱🏼‍♂", true); + emojiTrie.put("👱🏽‍♂️", true); + emojiTrie.put("👱🏽‍♂", true); + emojiTrie.put("👱🏾‍♂️", true); + emojiTrie.put("👱🏾‍♂", true); + emojiTrie.put("👱🏿‍♂️", true); + emojiTrie.put("👱🏿‍♂", true); + emojiTrie.put("🧓", true); + emojiTrie.put("🧓🏻", true); + emojiTrie.put("🧓🏼", true); + emojiTrie.put("🧓🏽", true); + emojiTrie.put("🧓🏾", true); + emojiTrie.put("🧓🏿", true); + emojiTrie.put("👴", true); + emojiTrie.put("👴🏻", true); + emojiTrie.put("👴🏼", true); + emojiTrie.put("👴🏽", true); + emojiTrie.put("👴🏾", true); + emojiTrie.put("👴🏿", true); + emojiTrie.put("👵", true); + emojiTrie.put("👵🏻", true); + emojiTrie.put("👵🏼", true); + emojiTrie.put("👵🏽", true); + emojiTrie.put("👵🏾", true); + emojiTrie.put("👵🏿", true); + emojiTrie.put("🙍", true); + emojiTrie.put("🙍🏻", true); + emojiTrie.put("🙍🏼", true); + emojiTrie.put("🙍🏽", true); + emojiTrie.put("🙍🏾", true); + emojiTrie.put("🙍🏿", true); + emojiTrie.put("🙍‍♂️", true); + emojiTrie.put("🙍‍♂", true); + emojiTrie.put("🙍🏻‍♂️", true); + emojiTrie.put("🙍🏻‍♂", true); + emojiTrie.put("🙍🏼‍♂️", true); + emojiTrie.put("🙍🏼‍♂", true); + emojiTrie.put("🙍🏽‍♂️", true); + emojiTrie.put("🙍🏽‍♂", true); + emojiTrie.put("🙍🏾‍♂️", true); + emojiTrie.put("🙍🏾‍♂", true); + emojiTrie.put("🙍🏿‍♂️", true); + emojiTrie.put("🙍🏿‍♂", true); + emojiTrie.put("🙍‍♀️", true); + emojiTrie.put("🙍‍♀", true); + emojiTrie.put("🙍🏻‍♀️", true); + emojiTrie.put("🙍🏻‍♀", true); + emojiTrie.put("🙍🏼‍♀️", true); + emojiTrie.put("🙍🏼‍♀", true); + emojiTrie.put("🙍🏽‍♀️", true); + emojiTrie.put("🙍🏽‍♀", true); + emojiTrie.put("🙍🏾‍♀️", true); + emojiTrie.put("🙍🏾‍♀", true); + emojiTrie.put("🙍🏿‍♀️", true); + emojiTrie.put("🙍🏿‍♀", true); + emojiTrie.put("🙎", true); + emojiTrie.put("🙎🏻", true); + emojiTrie.put("🙎🏼", true); + emojiTrie.put("🙎🏽", true); + emojiTrie.put("🙎🏾", true); + emojiTrie.put("🙎🏿", true); + emojiTrie.put("🙎‍♂️", true); + emojiTrie.put("🙎‍♂", true); + emojiTrie.put("🙎🏻‍♂️", true); + emojiTrie.put("🙎🏻‍♂", true); + emojiTrie.put("🙎🏼‍♂️", true); + emojiTrie.put("🙎🏼‍♂", true); + emojiTrie.put("🙎🏽‍♂️", true); + emojiTrie.put("🙎🏽‍♂", true); + emojiTrie.put("🙎🏾‍♂️", true); + emojiTrie.put("🙎🏾‍♂", true); + emojiTrie.put("🙎🏿‍♂️", true); + emojiTrie.put("🙎🏿‍♂", true); + emojiTrie.put("🙎‍♀️", true); + emojiTrie.put("🙎‍♀", true); + emojiTrie.put("🙎🏻‍♀️", true); + emojiTrie.put("🙎🏻‍♀", true); + emojiTrie.put("🙎🏼‍♀️", true); + emojiTrie.put("🙎🏼‍♀", true); + emojiTrie.put("🙎🏽‍♀️", true); + emojiTrie.put("🙎🏽‍♀", true); + emojiTrie.put("🙎🏾‍♀️", true); + emojiTrie.put("🙎🏾‍♀", true); + emojiTrie.put("🙎🏿‍♀️", true); + emojiTrie.put("🙎🏿‍♀", true); + emojiTrie.put("🙅", true); + emojiTrie.put("🙅🏻", true); + emojiTrie.put("🙅🏼", true); + emojiTrie.put("🙅🏽", true); + emojiTrie.put("🙅🏾", true); + emojiTrie.put("🙅🏿", true); + emojiTrie.put("🙅‍♂️", true); + emojiTrie.put("🙅‍♂", true); + emojiTrie.put("🙅🏻‍♂️", true); + emojiTrie.put("🙅🏻‍♂", true); + emojiTrie.put("🙅🏼‍♂️", true); + emojiTrie.put("🙅🏼‍♂", true); + emojiTrie.put("🙅🏽‍♂️", true); + emojiTrie.put("🙅🏽‍♂", true); + emojiTrie.put("🙅🏾‍♂️", true); + emojiTrie.put("🙅🏾‍♂", true); + emojiTrie.put("🙅🏿‍♂️", true); + emojiTrie.put("🙅🏿‍♂", true); + emojiTrie.put("🙅‍♀️", true); + emojiTrie.put("🙅‍♀", true); + emojiTrie.put("🙅🏻‍♀️", true); + emojiTrie.put("🙅🏻‍♀", true); + emojiTrie.put("🙅🏼‍♀️", true); + emojiTrie.put("🙅🏼‍♀", true); + emojiTrie.put("🙅🏽‍♀️", true); + emojiTrie.put("🙅🏽‍♀", true); + emojiTrie.put("🙅🏾‍♀️", true); + emojiTrie.put("🙅🏾‍♀", true); + emojiTrie.put("🙅🏿‍♀️", true); + emojiTrie.put("🙅🏿‍♀", true); + emojiTrie.put("🙆", true); + emojiTrie.put("🙆🏻", true); + emojiTrie.put("🙆🏼", true); + emojiTrie.put("🙆🏽", true); + emojiTrie.put("🙆🏾", true); + emojiTrie.put("🙆🏿", true); + emojiTrie.put("🙆‍♂️", true); + emojiTrie.put("🙆‍♂", true); + emojiTrie.put("🙆🏻‍♂️", true); + emojiTrie.put("🙆🏻‍♂", true); + emojiTrie.put("🙆🏼‍♂️", true); + emojiTrie.put("🙆🏼‍♂", true); + emojiTrie.put("🙆🏽‍♂️", true); + emojiTrie.put("🙆🏽‍♂", true); + emojiTrie.put("🙆🏾‍♂️", true); + emojiTrie.put("🙆🏾‍♂", true); + emojiTrie.put("🙆🏿‍♂️", true); + emojiTrie.put("🙆🏿‍♂", true); + emojiTrie.put("🙆‍♀️", true); + emojiTrie.put("🙆‍♀", true); + emojiTrie.put("🙆🏻‍♀️", true); + emojiTrie.put("🙆🏻‍♀", true); + emojiTrie.put("🙆🏼‍♀️", true); + emojiTrie.put("🙆🏼‍♀", true); + emojiTrie.put("🙆🏽‍♀️", true); + emojiTrie.put("🙆🏽‍♀", true); + emojiTrie.put("🙆🏾‍♀️", true); + emojiTrie.put("🙆🏾‍♀", true); + emojiTrie.put("🙆🏿‍♀️", true); + emojiTrie.put("🙆🏿‍♀", true); + emojiTrie.put("💁", true); + emojiTrie.put("💁🏻", true); + emojiTrie.put("💁🏼", true); + emojiTrie.put("💁🏽", true); + emojiTrie.put("💁🏾", true); + emojiTrie.put("💁🏿", true); + emojiTrie.put("💁‍♂️", true); + emojiTrie.put("💁‍♂", true); + emojiTrie.put("💁🏻‍♂️", true); + emojiTrie.put("💁🏻‍♂", true); + emojiTrie.put("💁🏼‍♂️", true); + emojiTrie.put("💁🏼‍♂", true); + emojiTrie.put("💁🏽‍♂️", true); + emojiTrie.put("💁🏽‍♂", true); + emojiTrie.put("💁🏾‍♂️", true); + emojiTrie.put("💁🏾‍♂", true); + emojiTrie.put("💁🏿‍♂️", true); + emojiTrie.put("💁🏿‍♂", true); + emojiTrie.put("💁‍♀️", true); + emojiTrie.put("💁‍♀", true); + emojiTrie.put("💁🏻‍♀️", true); + emojiTrie.put("💁🏻‍♀", true); + emojiTrie.put("💁🏼‍♀️", true); + emojiTrie.put("💁🏼‍♀", true); + emojiTrie.put("💁🏽‍♀️", true); + emojiTrie.put("💁🏽‍♀", true); + emojiTrie.put("💁🏾‍♀️", true); + emojiTrie.put("💁🏾‍♀", true); + emojiTrie.put("💁🏿‍♀️", true); + emojiTrie.put("💁🏿‍♀", true); + emojiTrie.put("🙋", true); + emojiTrie.put("🙋🏻", true); + emojiTrie.put("🙋🏼", true); + emojiTrie.put("🙋🏽", true); + emojiTrie.put("🙋🏾", true); + emojiTrie.put("🙋🏿", true); + emojiTrie.put("🙋‍♂️", true); + emojiTrie.put("🙋‍♂", true); + emojiTrie.put("🙋🏻‍♂️", true); + emojiTrie.put("🙋🏻‍♂", true); + emojiTrie.put("🙋🏼‍♂️", true); + emojiTrie.put("🙋🏼‍♂", true); + emojiTrie.put("🙋🏽‍♂️", true); + emojiTrie.put("🙋🏽‍♂", true); + emojiTrie.put("🙋🏾‍♂️", true); + emojiTrie.put("🙋🏾‍♂", true); + emojiTrie.put("🙋🏿‍♂️", true); + emojiTrie.put("🙋🏿‍♂", true); + emojiTrie.put("🙋‍♀️", true); + emojiTrie.put("🙋‍♀", true); + emojiTrie.put("🙋🏻‍♀️", true); + emojiTrie.put("🙋🏻‍♀", true); + emojiTrie.put("🙋🏼‍♀️", true); + emojiTrie.put("🙋🏼‍♀", true); + emojiTrie.put("🙋🏽‍♀️", true); + emojiTrie.put("🙋🏽‍♀", true); + emojiTrie.put("🙋🏾‍♀️", true); + emojiTrie.put("🙋🏾‍♀", true); + emojiTrie.put("🙋🏿‍♀️", true); + emojiTrie.put("🙋🏿‍♀", true); + emojiTrie.put("🧏", true); + emojiTrie.put("🧏🏻", true); + emojiTrie.put("🧏🏼", true); + emojiTrie.put("🧏🏽", true); + emojiTrie.put("🧏🏾", true); + emojiTrie.put("🧏🏿", true); + emojiTrie.put("🧏‍♂️", true); + emojiTrie.put("🧏‍♂", true); + emojiTrie.put("🧏🏻‍♂️", true); + emojiTrie.put("🧏🏻‍♂", true); + emojiTrie.put("🧏🏼‍♂️", true); + emojiTrie.put("🧏🏼‍♂", true); + emojiTrie.put("🧏🏽‍♂️", true); + emojiTrie.put("🧏🏽‍♂", true); + emojiTrie.put("🧏🏾‍♂️", true); + emojiTrie.put("🧏🏾‍♂", true); + emojiTrie.put("🧏🏿‍♂️", true); + emojiTrie.put("🧏🏿‍♂", true); + emojiTrie.put("🧏‍♀️", true); + emojiTrie.put("🧏‍♀", true); + emojiTrie.put("🧏🏻‍♀️", true); + emojiTrie.put("🧏🏻‍♀", true); + emojiTrie.put("🧏🏼‍♀️", true); + emojiTrie.put("🧏🏼‍♀", true); + emojiTrie.put("🧏🏽‍♀️", true); + emojiTrie.put("🧏🏽‍♀", true); + emojiTrie.put("🧏🏾‍♀️", true); + emojiTrie.put("🧏🏾‍♀", true); + emojiTrie.put("🧏🏿‍♀️", true); + emojiTrie.put("🧏🏿‍♀", true); + emojiTrie.put("🙇", true); + emojiTrie.put("🙇🏻", true); + emojiTrie.put("🙇🏼", true); + emojiTrie.put("🙇🏽", true); + emojiTrie.put("🙇🏾", true); + emojiTrie.put("🙇🏿", true); + emojiTrie.put("🙇‍♂️", true); + emojiTrie.put("🙇‍♂", true); + emojiTrie.put("🙇🏻‍♂️", true); + emojiTrie.put("🙇🏻‍♂", true); + emojiTrie.put("🙇🏼‍♂️", true); + emojiTrie.put("🙇🏼‍♂", true); + emojiTrie.put("🙇🏽‍♂️", true); + emojiTrie.put("🙇🏽‍♂", true); + emojiTrie.put("🙇🏾‍♂️", true); + emojiTrie.put("🙇🏾‍♂", true); + emojiTrie.put("🙇🏿‍♂️", true); + emojiTrie.put("🙇🏿‍♂", true); + emojiTrie.put("🙇‍♀️", true); + emojiTrie.put("🙇‍♀", true); + emojiTrie.put("🙇🏻‍♀️", true); + emojiTrie.put("🙇🏻‍♀", true); + emojiTrie.put("🙇🏼‍♀️", true); + emojiTrie.put("🙇🏼‍♀", true); + emojiTrie.put("🙇🏽‍♀️", true); + emojiTrie.put("🙇🏽‍♀", true); + emojiTrie.put("🙇🏾‍♀️", true); + emojiTrie.put("🙇🏾‍♀", true); + emojiTrie.put("🙇🏿‍♀️", true); + emojiTrie.put("🙇🏿‍♀", true); + emojiTrie.put("🤦", true); + emojiTrie.put("🤦🏻", true); + emojiTrie.put("🤦🏼", true); + emojiTrie.put("🤦🏽", true); + emojiTrie.put("🤦🏾", true); + emojiTrie.put("🤦🏿", true); + emojiTrie.put("🤦‍♂️", true); + emojiTrie.put("🤦‍♂", true); + emojiTrie.put("🤦🏻‍♂️", true); + emojiTrie.put("🤦🏻‍♂", true); + emojiTrie.put("🤦🏼‍♂️", true); + emojiTrie.put("🤦🏼‍♂", true); + emojiTrie.put("🤦🏽‍♂️", true); + emojiTrie.put("🤦🏽‍♂", true); + emojiTrie.put("🤦🏾‍♂️", true); + emojiTrie.put("🤦🏾‍♂", true); + emojiTrie.put("🤦🏿‍♂️", true); + emojiTrie.put("🤦🏿‍♂", true); + emojiTrie.put("🤦‍♀️", true); + emojiTrie.put("🤦‍♀", true); + emojiTrie.put("🤦🏻‍♀️", true); + emojiTrie.put("🤦🏻‍♀", true); + emojiTrie.put("🤦🏼‍♀️", true); + emojiTrie.put("🤦🏼‍♀", true); + emojiTrie.put("🤦🏽‍♀️", true); + emojiTrie.put("🤦🏽‍♀", true); + emojiTrie.put("🤦🏾‍♀️", true); + emojiTrie.put("🤦🏾‍♀", true); + emojiTrie.put("🤦🏿‍♀️", true); + emojiTrie.put("🤦🏿‍♀", true); + emojiTrie.put("🤷", true); + emojiTrie.put("🤷🏻", true); + emojiTrie.put("🤷🏼", true); + emojiTrie.put("🤷🏽", true); + emojiTrie.put("🤷🏾", true); + emojiTrie.put("🤷🏿", true); + emojiTrie.put("🤷‍♂️", true); + emojiTrie.put("🤷‍♂", true); + emojiTrie.put("🤷🏻‍♂️", true); + emojiTrie.put("🤷🏻‍♂", true); + emojiTrie.put("🤷🏼‍♂️", true); + emojiTrie.put("🤷🏼‍♂", true); + emojiTrie.put("🤷🏽‍♂️", true); + emojiTrie.put("🤷🏽‍♂", true); + emojiTrie.put("🤷🏾‍♂️", true); + emojiTrie.put("🤷🏾‍♂", true); + emojiTrie.put("🤷🏿‍♂️", true); + emojiTrie.put("🤷🏿‍♂", true); + emojiTrie.put("🤷‍♀️", true); + emojiTrie.put("🤷‍♀", true); + emojiTrie.put("🤷🏻‍♀️", true); + emojiTrie.put("🤷🏻‍♀", true); + emojiTrie.put("🤷🏼‍♀️", true); + emojiTrie.put("🤷🏼‍♀", true); + emojiTrie.put("🤷🏽‍♀️", true); + emojiTrie.put("🤷🏽‍♀", true); + emojiTrie.put("🤷🏾‍♀️", true); + emojiTrie.put("🤷🏾‍♀", true); + emojiTrie.put("🤷🏿‍♀️", true); + emojiTrie.put("🤷🏿‍♀", true); + emojiTrie.put("🧑‍⚕️", true); + emojiTrie.put("🧑‍⚕", true); + emojiTrie.put("🧑🏻‍⚕️", true); + emojiTrie.put("🧑🏻‍⚕", true); + emojiTrie.put("🧑🏼‍⚕️", true); + emojiTrie.put("🧑🏼‍⚕", true); + emojiTrie.put("🧑🏽‍⚕️", true); + emojiTrie.put("🧑🏽‍⚕", true); + emojiTrie.put("🧑🏾‍⚕️", true); + emojiTrie.put("🧑🏾‍⚕", true); + emojiTrie.put("🧑🏿‍⚕️", true); + emojiTrie.put("🧑🏿‍⚕", true); + emojiTrie.put("👨‍⚕️", true); + emojiTrie.put("👨‍⚕", true); + emojiTrie.put("👨🏻‍⚕️", true); + emojiTrie.put("👨🏻‍⚕", true); + emojiTrie.put("👨🏼‍⚕️", true); + emojiTrie.put("👨🏼‍⚕", true); + emojiTrie.put("👨🏽‍⚕️", true); + emojiTrie.put("👨🏽‍⚕", true); + emojiTrie.put("👨🏾‍⚕️", true); + emojiTrie.put("👨🏾‍⚕", true); + emojiTrie.put("👨🏿‍⚕️", true); + emojiTrie.put("👨🏿‍⚕", true); + emojiTrie.put("👩‍⚕️", true); + emojiTrie.put("👩‍⚕", true); + emojiTrie.put("👩🏻‍⚕️", true); + emojiTrie.put("👩🏻‍⚕", true); + emojiTrie.put("👩🏼‍⚕️", true); + emojiTrie.put("👩🏼‍⚕", true); + emojiTrie.put("👩🏽‍⚕️", true); + emojiTrie.put("👩🏽‍⚕", true); + emojiTrie.put("👩🏾‍⚕️", true); + emojiTrie.put("👩🏾‍⚕", true); + emojiTrie.put("👩🏿‍⚕️", true); + emojiTrie.put("👩🏿‍⚕", true); + emojiTrie.put("🧑‍🎓", true); + emojiTrie.put("🧑🏻‍🎓", true); + emojiTrie.put("🧑🏼‍🎓", true); + emojiTrie.put("🧑🏽‍🎓", true); + emojiTrie.put("🧑🏾‍🎓", true); + emojiTrie.put("🧑🏿‍🎓", true); + emojiTrie.put("👨‍🎓", true); + emojiTrie.put("👨🏻‍🎓", true); + emojiTrie.put("👨🏼‍🎓", true); + emojiTrie.put("👨🏽‍🎓", true); + emojiTrie.put("👨🏾‍🎓", true); + emojiTrie.put("👨🏿‍🎓", true); + emojiTrie.put("👩‍🎓", true); + emojiTrie.put("👩🏻‍🎓", true); + emojiTrie.put("👩🏼‍🎓", true); + emojiTrie.put("👩🏽‍🎓", true); + emojiTrie.put("👩🏾‍🎓", true); + emojiTrie.put("👩🏿‍🎓", true); + emojiTrie.put("🧑‍🏫", true); + emojiTrie.put("🧑🏻‍🏫", true); + emojiTrie.put("🧑🏼‍🏫", true); + emojiTrie.put("🧑🏽‍🏫", true); + emojiTrie.put("🧑🏾‍🏫", true); + emojiTrie.put("🧑🏿‍🏫", true); + emojiTrie.put("👨‍🏫", true); + emojiTrie.put("👨🏻‍🏫", true); + emojiTrie.put("👨🏼‍🏫", true); + emojiTrie.put("👨🏽‍🏫", true); + emojiTrie.put("👨🏾‍🏫", true); + emojiTrie.put("👨🏿‍🏫", true); + emojiTrie.put("👩‍🏫", true); + emojiTrie.put("👩🏻‍🏫", true); + emojiTrie.put("👩🏼‍🏫", true); + emojiTrie.put("👩🏽‍🏫", true); + emojiTrie.put("👩🏾‍🏫", true); + emojiTrie.put("👩🏿‍🏫", true); + emojiTrie.put("🧑‍⚖️", true); + emojiTrie.put("🧑‍⚖", true); + emojiTrie.put("🧑🏻‍⚖️", true); + emojiTrie.put("🧑🏻‍⚖", true); + emojiTrie.put("🧑🏼‍⚖️", true); + emojiTrie.put("🧑🏼‍⚖", true); + emojiTrie.put("🧑🏽‍⚖️", true); + emojiTrie.put("🧑🏽‍⚖", true); + emojiTrie.put("🧑🏾‍⚖️", true); + emojiTrie.put("🧑🏾‍⚖", true); + emojiTrie.put("🧑🏿‍⚖️", true); + emojiTrie.put("🧑🏿‍⚖", true); + emojiTrie.put("👨‍⚖️", true); + emojiTrie.put("👨‍⚖", true); + emojiTrie.put("👨🏻‍⚖️", true); + emojiTrie.put("👨🏻‍⚖", true); + emojiTrie.put("👨🏼‍⚖️", true); + emojiTrie.put("👨🏼‍⚖", true); + emojiTrie.put("👨🏽‍⚖️", true); + emojiTrie.put("👨🏽‍⚖", true); + emojiTrie.put("👨🏾‍⚖️", true); + emojiTrie.put("👨🏾‍⚖", true); + emojiTrie.put("👨🏿‍⚖️", true); + emojiTrie.put("👨🏿‍⚖", true); + emojiTrie.put("👩‍⚖️", true); + emojiTrie.put("👩‍⚖", true); + emojiTrie.put("👩🏻‍⚖️", true); + emojiTrie.put("👩🏻‍⚖", true); + emojiTrie.put("👩🏼‍⚖️", true); + emojiTrie.put("👩🏼‍⚖", true); + emojiTrie.put("👩🏽‍⚖️", true); + emojiTrie.put("👩🏽‍⚖", true); + emojiTrie.put("👩🏾‍⚖️", true); + emojiTrie.put("👩🏾‍⚖", true); + emojiTrie.put("👩🏿‍⚖️", true); + emojiTrie.put("👩🏿‍⚖", true); + emojiTrie.put("🧑‍🌾", true); + emojiTrie.put("🧑🏻‍🌾", true); + emojiTrie.put("🧑🏼‍🌾", true); + emojiTrie.put("🧑🏽‍🌾", true); + emojiTrie.put("🧑🏾‍🌾", true); + emojiTrie.put("🧑🏿‍🌾", true); + emojiTrie.put("👨‍🌾", true); + emojiTrie.put("👨🏻‍🌾", true); + emojiTrie.put("👨🏼‍🌾", true); + emojiTrie.put("👨🏽‍🌾", true); + emojiTrie.put("👨🏾‍🌾", true); + emojiTrie.put("👨🏿‍🌾", true); + emojiTrie.put("👩‍🌾", true); + emojiTrie.put("👩🏻‍🌾", true); + emojiTrie.put("👩🏼‍🌾", true); + emojiTrie.put("👩🏽‍🌾", true); + emojiTrie.put("👩🏾‍🌾", true); + emojiTrie.put("👩🏿‍🌾", true); + emojiTrie.put("🧑‍🍳", true); + emojiTrie.put("🧑🏻‍🍳", true); + emojiTrie.put("🧑🏼‍🍳", true); + emojiTrie.put("🧑🏽‍🍳", true); + emojiTrie.put("🧑🏾‍🍳", true); + emojiTrie.put("🧑🏿‍🍳", true); + emojiTrie.put("👨‍🍳", true); + emojiTrie.put("👨🏻‍🍳", true); + emojiTrie.put("👨🏼‍🍳", true); + emojiTrie.put("👨🏽‍🍳", true); + emojiTrie.put("👨🏾‍🍳", true); + emojiTrie.put("👨🏿‍🍳", true); + emojiTrie.put("👩‍🍳", true); + emojiTrie.put("👩🏻‍🍳", true); + emojiTrie.put("👩🏼‍🍳", true); + emojiTrie.put("👩🏽‍🍳", true); + emojiTrie.put("👩🏾‍🍳", true); + emojiTrie.put("👩🏿‍🍳", true); + emojiTrie.put("🧑‍🔧", true); + emojiTrie.put("🧑🏻‍🔧", true); + emojiTrie.put("🧑🏼‍🔧", true); + emojiTrie.put("🧑🏽‍🔧", true); + emojiTrie.put("🧑🏾‍🔧", true); + emojiTrie.put("🧑🏿‍🔧", true); + emojiTrie.put("👨‍🔧", true); + emojiTrie.put("👨🏻‍🔧", true); + emojiTrie.put("👨🏼‍🔧", true); + emojiTrie.put("👨🏽‍🔧", true); + emojiTrie.put("👨🏾‍🔧", true); + emojiTrie.put("👨🏿‍🔧", true); + emojiTrie.put("👩‍🔧", true); + emojiTrie.put("👩🏻‍🔧", true); + emojiTrie.put("👩🏼‍🔧", true); + emojiTrie.put("👩🏽‍🔧", true); + emojiTrie.put("👩🏾‍🔧", true); + emojiTrie.put("👩🏿‍🔧", true); + emojiTrie.put("🧑‍🏭", true); + emojiTrie.put("🧑🏻‍🏭", true); + emojiTrie.put("🧑🏼‍🏭", true); + emojiTrie.put("🧑🏽‍🏭", true); + emojiTrie.put("🧑🏾‍🏭", true); + emojiTrie.put("🧑🏿‍🏭", true); + emojiTrie.put("👨‍🏭", true); + emojiTrie.put("👨🏻‍🏭", true); + emojiTrie.put("👨🏼‍🏭", true); + emojiTrie.put("👨🏽‍🏭", true); + emojiTrie.put("👨🏾‍🏭", true); + emojiTrie.put("👨🏿‍🏭", true); + emojiTrie.put("👩‍🏭", true); + emojiTrie.put("👩🏻‍🏭", true); + emojiTrie.put("👩🏼‍🏭", true); + emojiTrie.put("👩🏽‍🏭", true); + emojiTrie.put("👩🏾‍🏭", true); + emojiTrie.put("👩🏿‍🏭", true); + emojiTrie.put("🧑‍💼", true); + emojiTrie.put("🧑🏻‍💼", true); + emojiTrie.put("🧑🏼‍💼", true); + emojiTrie.put("🧑🏽‍💼", true); + emojiTrie.put("🧑🏾‍💼", true); + emojiTrie.put("🧑🏿‍💼", true); + emojiTrie.put("👨‍💼", true); + emojiTrie.put("👨🏻‍💼", true); + emojiTrie.put("👨🏼‍💼", true); + emojiTrie.put("👨🏽‍💼", true); + emojiTrie.put("👨🏾‍💼", true); + emojiTrie.put("👨🏿‍💼", true); + emojiTrie.put("👩‍💼", true); + emojiTrie.put("👩🏻‍💼", true); + emojiTrie.put("👩🏼‍💼", true); + emojiTrie.put("👩🏽‍💼", true); + emojiTrie.put("👩🏾‍💼", true); + emojiTrie.put("👩🏿‍💼", true); + emojiTrie.put("🧑‍🔬", true); + emojiTrie.put("🧑🏻‍🔬", true); + emojiTrie.put("🧑🏼‍🔬", true); + emojiTrie.put("🧑🏽‍🔬", true); + emojiTrie.put("🧑🏾‍🔬", true); + emojiTrie.put("🧑🏿‍🔬", true); + emojiTrie.put("👨‍🔬", true); + emojiTrie.put("👨🏻‍🔬", true); + emojiTrie.put("👨🏼‍🔬", true); + emojiTrie.put("👨🏽‍🔬", true); + emojiTrie.put("👨🏾‍🔬", true); + emojiTrie.put("👨🏿‍🔬", true); + emojiTrie.put("👩‍🔬", true); + emojiTrie.put("👩🏻‍🔬", true); + emojiTrie.put("👩🏼‍🔬", true); + emojiTrie.put("👩🏽‍🔬", true); + emojiTrie.put("👩🏾‍🔬", true); + emojiTrie.put("👩🏿‍🔬", true); + emojiTrie.put("🧑‍💻", true); + emojiTrie.put("🧑🏻‍💻", true); + emojiTrie.put("🧑🏼‍💻", true); + emojiTrie.put("🧑🏽‍💻", true); + emojiTrie.put("🧑🏾‍💻", true); + emojiTrie.put("🧑🏿‍💻", true); + emojiTrie.put("👨‍💻", true); + emojiTrie.put("👨🏻‍💻", true); + emojiTrie.put("👨🏼‍💻", true); + emojiTrie.put("👨🏽‍💻", true); + emojiTrie.put("👨🏾‍💻", true); + emojiTrie.put("👨🏿‍💻", true); + emojiTrie.put("👩‍💻", true); + emojiTrie.put("👩🏻‍💻", true); + emojiTrie.put("👩🏼‍💻", true); + emojiTrie.put("👩🏽‍💻", true); + emojiTrie.put("👩🏾‍💻", true); + emojiTrie.put("👩🏿‍💻", true); + emojiTrie.put("🧑‍🎤", true); + emojiTrie.put("🧑🏻‍🎤", true); + emojiTrie.put("🧑🏼‍🎤", true); + emojiTrie.put("🧑🏽‍🎤", true); + emojiTrie.put("🧑🏾‍🎤", true); + emojiTrie.put("🧑🏿‍🎤", true); + emojiTrie.put("👨‍🎤", true); + emojiTrie.put("👨🏻‍🎤", true); + emojiTrie.put("👨🏼‍🎤", true); + emojiTrie.put("👨🏽‍🎤", true); + emojiTrie.put("👨🏾‍🎤", true); + emojiTrie.put("👨🏿‍🎤", true); + emojiTrie.put("👩‍🎤", true); + emojiTrie.put("👩🏻‍🎤", true); + emojiTrie.put("👩🏼‍🎤", true); + emojiTrie.put("👩🏽‍🎤", true); + emojiTrie.put("👩🏾‍🎤", true); + emojiTrie.put("👩🏿‍🎤", true); + emojiTrie.put("🧑‍🎨", true); + emojiTrie.put("🧑🏻‍🎨", true); + emojiTrie.put("🧑🏼‍🎨", true); + emojiTrie.put("🧑🏽‍🎨", true); + emojiTrie.put("🧑🏾‍🎨", true); + emojiTrie.put("🧑🏿‍🎨", true); + emojiTrie.put("👨‍🎨", true); + emojiTrie.put("👨🏻‍🎨", true); + emojiTrie.put("👨🏼‍🎨", true); + emojiTrie.put("👨🏽‍🎨", true); + emojiTrie.put("👨🏾‍🎨", true); + emojiTrie.put("👨🏿‍🎨", true); + emojiTrie.put("👩‍🎨", true); + emojiTrie.put("👩🏻‍🎨", true); + emojiTrie.put("👩🏼‍🎨", true); + emojiTrie.put("👩🏽‍🎨", true); + emojiTrie.put("👩🏾‍🎨", true); + emojiTrie.put("👩🏿‍🎨", true); + emojiTrie.put("🧑‍✈️", true); + emojiTrie.put("🧑‍✈", true); + emojiTrie.put("🧑🏻‍✈️", true); + emojiTrie.put("🧑🏻‍✈", true); + emojiTrie.put("🧑🏼‍✈️", true); + emojiTrie.put("🧑🏼‍✈", true); + emojiTrie.put("🧑🏽‍✈️", true); + emojiTrie.put("🧑🏽‍✈", true); + emojiTrie.put("🧑🏾‍✈️", true); + emojiTrie.put("🧑🏾‍✈", true); + emojiTrie.put("🧑🏿‍✈️", true); + emojiTrie.put("🧑🏿‍✈", true); + emojiTrie.put("👨‍✈️", true); + emojiTrie.put("👨‍✈", true); + emojiTrie.put("👨🏻‍✈️", true); + emojiTrie.put("👨🏻‍✈", true); + emojiTrie.put("👨🏼‍✈️", true); + emojiTrie.put("👨🏼‍✈", true); + emojiTrie.put("👨🏽‍✈️", true); + emojiTrie.put("👨🏽‍✈", true); + emojiTrie.put("👨🏾‍✈️", true); + emojiTrie.put("👨🏾‍✈", true); + emojiTrie.put("👨🏿‍✈️", true); + emojiTrie.put("👨🏿‍✈", true); + emojiTrie.put("👩‍✈️", true); + emojiTrie.put("👩‍✈", true); + emojiTrie.put("👩🏻‍✈️", true); + emojiTrie.put("👩🏻‍✈", true); + emojiTrie.put("👩🏼‍✈️", true); + emojiTrie.put("👩🏼‍✈", true); + emojiTrie.put("👩🏽‍✈️", true); + emojiTrie.put("👩🏽‍✈", true); + emojiTrie.put("👩🏾‍✈️", true); + emojiTrie.put("👩🏾‍✈", true); + emojiTrie.put("👩🏿‍✈️", true); + emojiTrie.put("👩🏿‍✈", true); + emojiTrie.put("🧑‍🚀", true); + emojiTrie.put("🧑🏻‍🚀", true); + emojiTrie.put("🧑🏼‍🚀", true); + emojiTrie.put("🧑🏽‍🚀", true); + emojiTrie.put("🧑🏾‍🚀", true); + emojiTrie.put("🧑🏿‍🚀", true); + emojiTrie.put("👨‍🚀", true); + emojiTrie.put("👨🏻‍🚀", true); + emojiTrie.put("👨🏼‍🚀", true); + emojiTrie.put("👨🏽‍🚀", true); + emojiTrie.put("👨🏾‍🚀", true); + emojiTrie.put("👨🏿‍🚀", true); + emojiTrie.put("👩‍🚀", true); + emojiTrie.put("👩🏻‍🚀", true); + emojiTrie.put("👩🏼‍🚀", true); + emojiTrie.put("👩🏽‍🚀", true); + emojiTrie.put("👩🏾‍🚀", true); + emojiTrie.put("👩🏿‍🚀", true); + emojiTrie.put("🧑‍🚒", true); + emojiTrie.put("🧑🏻‍🚒", true); + emojiTrie.put("🧑🏼‍🚒", true); + emojiTrie.put("🧑🏽‍🚒", true); + emojiTrie.put("🧑🏾‍🚒", true); + emojiTrie.put("🧑🏿‍🚒", true); + emojiTrie.put("👨‍🚒", true); + emojiTrie.put("👨🏻‍🚒", true); + emojiTrie.put("👨🏼‍🚒", true); + emojiTrie.put("👨🏽‍🚒", true); + emojiTrie.put("👨🏾‍🚒", true); + emojiTrie.put("👨🏿‍🚒", true); + emojiTrie.put("👩‍🚒", true); + emojiTrie.put("👩🏻‍🚒", true); + emojiTrie.put("👩🏼‍🚒", true); + emojiTrie.put("👩🏽‍🚒", true); + emojiTrie.put("👩🏾‍🚒", true); + emojiTrie.put("👩🏿‍🚒", true); + emojiTrie.put("👮", true); + emojiTrie.put("👮🏻", true); + emojiTrie.put("👮🏼", true); + emojiTrie.put("👮🏽", true); + emojiTrie.put("👮🏾", true); + emojiTrie.put("👮🏿", true); + emojiTrie.put("👮‍♂️", true); + emojiTrie.put("👮‍♂", true); + emojiTrie.put("👮🏻‍♂️", true); + emojiTrie.put("👮🏻‍♂", true); + emojiTrie.put("👮🏼‍♂️", true); + emojiTrie.put("👮🏼‍♂", true); + emojiTrie.put("👮🏽‍♂️", true); + emojiTrie.put("👮🏽‍♂", true); + emojiTrie.put("👮🏾‍♂️", true); + emojiTrie.put("👮🏾‍♂", true); + emojiTrie.put("👮🏿‍♂️", true); + emojiTrie.put("👮🏿‍♂", true); + emojiTrie.put("👮‍♀️", true); + emojiTrie.put("👮‍♀", true); + emojiTrie.put("👮🏻‍♀️", true); + emojiTrie.put("👮🏻‍♀", true); + emojiTrie.put("👮🏼‍♀️", true); + emojiTrie.put("👮🏼‍♀", true); + emojiTrie.put("👮🏽‍♀️", true); + emojiTrie.put("👮🏽‍♀", true); + emojiTrie.put("👮🏾‍♀️", true); + emojiTrie.put("👮🏾‍♀", true); + emojiTrie.put("👮🏿‍♀️", true); + emojiTrie.put("👮🏿‍♀", true); + emojiTrie.put("🕵️", true); + emojiTrie.put("🕵", true); + emojiTrie.put("🕵🏻", true); + emojiTrie.put("🕵🏼", true); + emojiTrie.put("🕵🏽", true); + emojiTrie.put("🕵🏾", true); + emojiTrie.put("🕵🏿", true); + emojiTrie.put("🕵️‍♂️", true); + emojiTrie.put("🕵‍♂️", true); + emojiTrie.put("🕵️‍♂", true); + emojiTrie.put("🕵‍♂", true); + emojiTrie.put("🕵🏻‍♂️", true); + emojiTrie.put("🕵🏻‍♂", true); + emojiTrie.put("🕵🏼‍♂️", true); + emojiTrie.put("🕵🏼‍♂", true); + emojiTrie.put("🕵🏽‍♂️", true); + emojiTrie.put("🕵🏽‍♂", true); + emojiTrie.put("🕵🏾‍♂️", true); + emojiTrie.put("🕵🏾‍♂", true); + emojiTrie.put("🕵🏿‍♂️", true); + emojiTrie.put("🕵🏿‍♂", true); + emojiTrie.put("🕵️‍♀️", true); + emojiTrie.put("🕵‍♀️", true); + emojiTrie.put("🕵️‍♀", true); + emojiTrie.put("🕵‍♀", true); + emojiTrie.put("🕵🏻‍♀️", true); + emojiTrie.put("🕵🏻‍♀", true); + emojiTrie.put("🕵🏼‍♀️", true); + emojiTrie.put("🕵🏼‍♀", true); + emojiTrie.put("🕵🏽‍♀️", true); + emojiTrie.put("🕵🏽‍♀", true); + emojiTrie.put("🕵🏾‍♀️", true); + emojiTrie.put("🕵🏾‍♀", true); + emojiTrie.put("🕵🏿‍♀️", true); + emojiTrie.put("🕵🏿‍♀", true); + emojiTrie.put("💂", true); + emojiTrie.put("💂🏻", true); + emojiTrie.put("💂🏼", true); + emojiTrie.put("💂🏽", true); + emojiTrie.put("💂🏾", true); + emojiTrie.put("💂🏿", true); + emojiTrie.put("💂‍♂️", true); + emojiTrie.put("💂‍♂", true); + emojiTrie.put("💂🏻‍♂️", true); + emojiTrie.put("💂🏻‍♂", true); + emojiTrie.put("💂🏼‍♂️", true); + emojiTrie.put("💂🏼‍♂", true); + emojiTrie.put("💂🏽‍♂️", true); + emojiTrie.put("💂🏽‍♂", true); + emojiTrie.put("💂🏾‍♂️", true); + emojiTrie.put("💂🏾‍♂", true); + emojiTrie.put("💂🏿‍♂️", true); + emojiTrie.put("💂🏿‍♂", true); + emojiTrie.put("💂‍♀️", true); + emojiTrie.put("💂‍♀", true); + emojiTrie.put("💂🏻‍♀️", true); + emojiTrie.put("💂🏻‍♀", true); + emojiTrie.put("💂🏼‍♀️", true); + emojiTrie.put("💂🏼‍♀", true); + emojiTrie.put("💂🏽‍♀️", true); + emojiTrie.put("💂🏽‍♀", true); + emojiTrie.put("💂🏾‍♀️", true); + emojiTrie.put("💂🏾‍♀", true); + emojiTrie.put("💂🏿‍♀️", true); + emojiTrie.put("💂🏿‍♀", true); + emojiTrie.put("🥷", true); + emojiTrie.put("🥷🏻", true); + emojiTrie.put("🥷🏼", true); + emojiTrie.put("🥷🏽", true); + emojiTrie.put("🥷🏾", true); + emojiTrie.put("🥷🏿", true); + emojiTrie.put("👷", true); + emojiTrie.put("👷🏻", true); + emojiTrie.put("👷🏼", true); + emojiTrie.put("👷🏽", true); + emojiTrie.put("👷🏾", true); + emojiTrie.put("👷🏿", true); + emojiTrie.put("👷‍♂️", true); + emojiTrie.put("👷‍♂", true); + emojiTrie.put("👷🏻‍♂️", true); + emojiTrie.put("👷🏻‍♂", true); + emojiTrie.put("👷🏼‍♂️", true); + emojiTrie.put("👷🏼‍♂", true); + emojiTrie.put("👷🏽‍♂️", true); + emojiTrie.put("👷🏽‍♂", true); + emojiTrie.put("👷🏾‍♂️", true); + emojiTrie.put("👷🏾‍♂", true); + emojiTrie.put("👷🏿‍♂️", true); + emojiTrie.put("👷🏿‍♂", true); + emojiTrie.put("👷‍♀️", true); + emojiTrie.put("👷‍♀", true); + emojiTrie.put("👷🏻‍♀️", true); + emojiTrie.put("👷🏻‍♀", true); + emojiTrie.put("👷🏼‍♀️", true); + emojiTrie.put("👷🏼‍♀", true); + emojiTrie.put("👷🏽‍♀️", true); + emojiTrie.put("👷🏽‍♀", true); + emojiTrie.put("👷🏾‍♀️", true); + emojiTrie.put("👷🏾‍♀", true); + emojiTrie.put("👷🏿‍♀️", true); + emojiTrie.put("👷🏿‍♀", true); + emojiTrie.put("🫅", true); + emojiTrie.put("🫅🏻", true); + emojiTrie.put("🫅🏼", true); + emojiTrie.put("🫅🏽", true); + emojiTrie.put("🫅🏾", true); + emojiTrie.put("🫅🏿", true); + emojiTrie.put("🤴", true); + emojiTrie.put("🤴🏻", true); + emojiTrie.put("🤴🏼", true); + emojiTrie.put("🤴🏽", true); + emojiTrie.put("🤴🏾", true); + emojiTrie.put("🤴🏿", true); + emojiTrie.put("👸", true); + emojiTrie.put("👸🏻", true); + emojiTrie.put("👸🏼", true); + emojiTrie.put("👸🏽", true); + emojiTrie.put("👸🏾", true); + emojiTrie.put("👸🏿", true); + emojiTrie.put("👳", true); + emojiTrie.put("👳🏻", true); + emojiTrie.put("👳🏼", true); + emojiTrie.put("👳🏽", true); + emojiTrie.put("👳🏾", true); + emojiTrie.put("👳🏿", true); + emojiTrie.put("👳‍♂️", true); + emojiTrie.put("👳‍♂", true); + emojiTrie.put("👳🏻‍♂️", true); + emojiTrie.put("👳🏻‍♂", true); + emojiTrie.put("👳🏼‍♂️", true); + emojiTrie.put("👳🏼‍♂", true); + emojiTrie.put("👳🏽‍♂️", true); + emojiTrie.put("👳🏽‍♂", true); + emojiTrie.put("👳🏾‍♂️", true); + emojiTrie.put("👳🏾‍♂", true); + emojiTrie.put("👳🏿‍♂️", true); + emojiTrie.put("👳🏿‍♂", true); + emojiTrie.put("👳‍♀️", true); + emojiTrie.put("👳‍♀", true); + emojiTrie.put("👳🏻‍♀️", true); + emojiTrie.put("👳🏻‍♀", true); + emojiTrie.put("👳🏼‍♀️", true); + emojiTrie.put("👳🏼‍♀", true); + emojiTrie.put("👳🏽‍♀️", true); + emojiTrie.put("👳🏽‍♀", true); + emojiTrie.put("👳🏾‍♀️", true); + emojiTrie.put("👳🏾‍♀", true); + emojiTrie.put("👳🏿‍♀️", true); + emojiTrie.put("👳🏿‍♀", true); + emojiTrie.put("👲", true); + emojiTrie.put("👲🏻", true); + emojiTrie.put("👲🏼", true); + emojiTrie.put("👲🏽", true); + emojiTrie.put("👲🏾", true); + emojiTrie.put("👲🏿", true); + emojiTrie.put("🧕", true); + emojiTrie.put("🧕🏻", true); + emojiTrie.put("🧕🏼", true); + emojiTrie.put("🧕🏽", true); + emojiTrie.put("🧕🏾", true); + emojiTrie.put("🧕🏿", true); + emojiTrie.put("🤵", true); + emojiTrie.put("🤵🏻", true); + emojiTrie.put("🤵🏼", true); + emojiTrie.put("🤵🏽", true); + emojiTrie.put("🤵🏾", true); + emojiTrie.put("🤵🏿", true); + emojiTrie.put("🤵‍♂️", true); + emojiTrie.put("🤵‍♂", true); + emojiTrie.put("🤵🏻‍♂️", true); + emojiTrie.put("🤵🏻‍♂", true); + emojiTrie.put("🤵🏼‍♂️", true); + emojiTrie.put("🤵🏼‍♂", true); + emojiTrie.put("🤵🏽‍♂️", true); + emojiTrie.put("🤵🏽‍♂", true); + emojiTrie.put("🤵🏾‍♂️", true); + emojiTrie.put("🤵🏾‍♂", true); + emojiTrie.put("🤵🏿‍♂️", true); + emojiTrie.put("🤵🏿‍♂", true); + emojiTrie.put("🤵‍♀️", true); + emojiTrie.put("🤵‍♀", true); + emojiTrie.put("🤵🏻‍♀️", true); + emojiTrie.put("🤵🏻‍♀", true); + emojiTrie.put("🤵🏼‍♀️", true); + emojiTrie.put("🤵🏼‍♀", true); + emojiTrie.put("🤵🏽‍♀️", true); + emojiTrie.put("🤵🏽‍♀", true); + emojiTrie.put("🤵🏾‍♀️", true); + emojiTrie.put("🤵🏾‍♀", true); + emojiTrie.put("🤵🏿‍♀️", true); + emojiTrie.put("🤵🏿‍♀", true); + emojiTrie.put("👰", true); + emojiTrie.put("👰🏻", true); + emojiTrie.put("👰🏼", true); + emojiTrie.put("👰🏽", true); + emojiTrie.put("👰🏾", true); + emojiTrie.put("👰🏿", true); + emojiTrie.put("👰‍♂️", true); + emojiTrie.put("👰‍♂", true); + emojiTrie.put("👰🏻‍♂️", true); + emojiTrie.put("👰🏻‍♂", true); + emojiTrie.put("👰🏼‍♂️", true); + emojiTrie.put("👰🏼‍♂", true); + emojiTrie.put("👰🏽‍♂️", true); + emojiTrie.put("👰🏽‍♂", true); + emojiTrie.put("👰🏾‍♂️", true); + emojiTrie.put("👰🏾‍♂", true); + emojiTrie.put("👰🏿‍♂️", true); + emojiTrie.put("👰🏿‍♂", true); + emojiTrie.put("👰‍♀️", true); + emojiTrie.put("👰‍♀", true); + emojiTrie.put("👰🏻‍♀️", true); + emojiTrie.put("👰🏻‍♀", true); + emojiTrie.put("👰🏼‍♀️", true); + emojiTrie.put("👰🏼‍♀", true); + emojiTrie.put("👰🏽‍♀️", true); + emojiTrie.put("👰🏽‍♀", true); + emojiTrie.put("👰🏾‍♀️", true); + emojiTrie.put("👰🏾‍♀", true); + emojiTrie.put("👰🏿‍♀️", true); + emojiTrie.put("👰🏿‍♀", true); + emojiTrie.put("🤰", true); + emojiTrie.put("🤰🏻", true); + emojiTrie.put("🤰🏼", true); + emojiTrie.put("🤰🏽", true); + emojiTrie.put("🤰🏾", true); + emojiTrie.put("🤰🏿", true); + emojiTrie.put("🫃", true); + emojiTrie.put("🫃🏻", true); + emojiTrie.put("🫃🏼", true); + emojiTrie.put("🫃🏽", true); + emojiTrie.put("🫃🏾", true); + emojiTrie.put("🫃🏿", true); + emojiTrie.put("🫄", true); + emojiTrie.put("🫄🏻", true); + emojiTrie.put("🫄🏼", true); + emojiTrie.put("🫄🏽", true); + emojiTrie.put("🫄🏾", true); + emojiTrie.put("🫄🏿", true); + emojiTrie.put("🤱", true); + emojiTrie.put("🤱🏻", true); + emojiTrie.put("🤱🏼", true); + emojiTrie.put("🤱🏽", true); + emojiTrie.put("🤱🏾", true); + emojiTrie.put("🤱🏿", true); + emojiTrie.put("👩‍🍼", true); + emojiTrie.put("👩🏻‍🍼", true); + emojiTrie.put("👩🏼‍🍼", true); + emojiTrie.put("👩🏽‍🍼", true); + emojiTrie.put("👩🏾‍🍼", true); + emojiTrie.put("👩🏿‍🍼", true); + emojiTrie.put("👨‍🍼", true); + emojiTrie.put("👨🏻‍🍼", true); + emojiTrie.put("👨🏼‍🍼", true); + emojiTrie.put("👨🏽‍🍼", true); + emojiTrie.put("👨🏾‍🍼", true); + emojiTrie.put("👨🏿‍🍼", true); + emojiTrie.put("🧑‍🍼", true); + emojiTrie.put("🧑🏻‍🍼", true); + emojiTrie.put("🧑🏼‍🍼", true); + emojiTrie.put("🧑🏽‍🍼", true); + emojiTrie.put("🧑🏾‍🍼", true); + emojiTrie.put("🧑🏿‍🍼", true); + emojiTrie.put("👼", true); + emojiTrie.put("👼🏻", true); + emojiTrie.put("👼🏼", true); + emojiTrie.put("👼🏽", true); + emojiTrie.put("👼🏾", true); + emojiTrie.put("👼🏿", true); + emojiTrie.put("🎅", true); + emojiTrie.put("🎅🏻", true); + emojiTrie.put("🎅🏼", true); + emojiTrie.put("🎅🏽", true); + emojiTrie.put("🎅🏾", true); + emojiTrie.put("🎅🏿", true); + emojiTrie.put("🤶", true); + emojiTrie.put("🤶🏻", true); + emojiTrie.put("🤶🏼", true); + emojiTrie.put("🤶🏽", true); + emojiTrie.put("🤶🏾", true); + emojiTrie.put("🤶🏿", true); + emojiTrie.put("🧑‍🎄", true); + emojiTrie.put("🧑🏻‍🎄", true); + emojiTrie.put("🧑🏼‍🎄", true); + emojiTrie.put("🧑🏽‍🎄", true); + emojiTrie.put("🧑🏾‍🎄", true); + emojiTrie.put("🧑🏿‍🎄", true); + emojiTrie.put("🦸", true); + emojiTrie.put("🦸🏻", true); + emojiTrie.put("🦸🏼", true); + emojiTrie.put("🦸🏽", true); + emojiTrie.put("🦸🏾", true); + emojiTrie.put("🦸🏿", true); + emojiTrie.put("🦸‍♂️", true); + emojiTrie.put("🦸‍♂", true); + emojiTrie.put("🦸🏻‍♂️", true); + emojiTrie.put("🦸🏻‍♂", true); + emojiTrie.put("🦸🏼‍♂️", true); + emojiTrie.put("🦸🏼‍♂", true); + emojiTrie.put("🦸🏽‍♂️", true); + emojiTrie.put("🦸🏽‍♂", true); + emojiTrie.put("🦸🏾‍♂️", true); + emojiTrie.put("🦸🏾‍♂", true); + emojiTrie.put("🦸🏿‍♂️", true); + emojiTrie.put("🦸🏿‍♂", true); + emojiTrie.put("🦸‍♀️", true); + emojiTrie.put("🦸‍♀", true); + emojiTrie.put("🦸🏻‍♀️", true); + emojiTrie.put("🦸🏻‍♀", true); + emojiTrie.put("🦸🏼‍♀️", true); + emojiTrie.put("🦸🏼‍♀", true); + emojiTrie.put("🦸🏽‍♀️", true); + emojiTrie.put("🦸🏽‍♀", true); + emojiTrie.put("🦸🏾‍♀️", true); + emojiTrie.put("🦸🏾‍♀", true); + emojiTrie.put("🦸🏿‍♀️", true); + emojiTrie.put("🦸🏿‍♀", true); + emojiTrie.put("🦹", true); + emojiTrie.put("🦹🏻", true); + emojiTrie.put("🦹🏼", true); + emojiTrie.put("🦹🏽", true); + emojiTrie.put("🦹🏾", true); + emojiTrie.put("🦹🏿", true); + emojiTrie.put("🦹‍♂️", true); + emojiTrie.put("🦹‍♂", true); + emojiTrie.put("🦹🏻‍♂️", true); + emojiTrie.put("🦹🏻‍♂", true); + emojiTrie.put("🦹🏼‍♂️", true); + emojiTrie.put("🦹🏼‍♂", true); + emojiTrie.put("🦹🏽‍♂️", true); + emojiTrie.put("🦹🏽‍♂", true); + emojiTrie.put("🦹🏾‍♂️", true); + emojiTrie.put("🦹🏾‍♂", true); + emojiTrie.put("🦹🏿‍♂️", true); + emojiTrie.put("🦹🏿‍♂", true); + emojiTrie.put("🦹‍♀️", true); + emojiTrie.put("🦹‍♀", true); + emojiTrie.put("🦹🏻‍♀️", true); + emojiTrie.put("🦹🏻‍♀", true); + emojiTrie.put("🦹🏼‍♀️", true); + emojiTrie.put("🦹🏼‍♀", true); + emojiTrie.put("🦹🏽‍♀️", true); + emojiTrie.put("🦹🏽‍♀", true); + emojiTrie.put("🦹🏾‍♀️", true); + emojiTrie.put("🦹🏾‍♀", true); + emojiTrie.put("🦹🏿‍♀️", true); + emojiTrie.put("🦹🏿‍♀", true); + emojiTrie.put("🧙", true); + emojiTrie.put("🧙🏻", true); + emojiTrie.put("🧙🏼", true); + emojiTrie.put("🧙🏽", true); + emojiTrie.put("🧙🏾", true); + emojiTrie.put("🧙🏿", true); + emojiTrie.put("🧙‍♂️", true); + emojiTrie.put("🧙‍♂", true); + emojiTrie.put("🧙🏻‍♂️", true); + emojiTrie.put("🧙🏻‍♂", true); + emojiTrie.put("🧙🏼‍♂️", true); + emojiTrie.put("🧙🏼‍♂", true); + emojiTrie.put("🧙🏽‍♂️", true); + emojiTrie.put("🧙🏽‍♂", true); + emojiTrie.put("🧙🏾‍♂️", true); + emojiTrie.put("🧙🏾‍♂", true); + emojiTrie.put("🧙🏿‍♂️", true); + emojiTrie.put("🧙🏿‍♂", true); + emojiTrie.put("🧙‍♀️", true); + emojiTrie.put("🧙‍♀", true); + emojiTrie.put("🧙🏻‍♀️", true); + emojiTrie.put("🧙🏻‍♀", true); + emojiTrie.put("🧙🏼‍♀️", true); + emojiTrie.put("🧙🏼‍♀", true); + emojiTrie.put("🧙🏽‍♀️", true); + emojiTrie.put("🧙🏽‍♀", true); + emojiTrie.put("🧙🏾‍♀️", true); + emojiTrie.put("🧙🏾‍♀", true); + emojiTrie.put("🧙🏿‍♀️", true); + emojiTrie.put("🧙🏿‍♀", true); + emojiTrie.put("🧚", true); + emojiTrie.put("🧚🏻", true); + emojiTrie.put("🧚🏼", true); + emojiTrie.put("🧚🏽", true); + emojiTrie.put("🧚🏾", true); + emojiTrie.put("🧚🏿", true); + emojiTrie.put("🧚‍♂️", true); + emojiTrie.put("🧚‍♂", true); + emojiTrie.put("🧚🏻‍♂️", true); + emojiTrie.put("🧚🏻‍♂", true); + emojiTrie.put("🧚🏼‍♂️", true); + emojiTrie.put("🧚🏼‍♂", true); + emojiTrie.put("🧚🏽‍♂️", true); + emojiTrie.put("🧚🏽‍♂", true); + emojiTrie.put("🧚🏾‍♂️", true); + emojiTrie.put("🧚🏾‍♂", true); + emojiTrie.put("🧚🏿‍♂️", true); + emojiTrie.put("🧚🏿‍♂", true); + emojiTrie.put("🧚‍♀️", true); + emojiTrie.put("🧚‍♀", true); + emojiTrie.put("🧚🏻‍♀️", true); + emojiTrie.put("🧚🏻‍♀", true); + emojiTrie.put("🧚🏼‍♀️", true); + emojiTrie.put("🧚🏼‍♀", true); + emojiTrie.put("🧚🏽‍♀️", true); + emojiTrie.put("🧚🏽‍♀", true); + emojiTrie.put("🧚🏾‍♀️", true); + emojiTrie.put("🧚🏾‍♀", true); + emojiTrie.put("🧚🏿‍♀️", true); + emojiTrie.put("🧚🏿‍♀", true); + emojiTrie.put("🧛", true); + emojiTrie.put("🧛🏻", true); + emojiTrie.put("🧛🏼", true); + emojiTrie.put("🧛🏽", true); + emojiTrie.put("🧛🏾", true); + emojiTrie.put("🧛🏿", true); + emojiTrie.put("🧛‍♂️", true); + emojiTrie.put("🧛‍♂", true); + emojiTrie.put("🧛🏻‍♂️", true); + emojiTrie.put("🧛🏻‍♂", true); + emojiTrie.put("🧛🏼‍♂️", true); + emojiTrie.put("🧛🏼‍♂", true); + emojiTrie.put("🧛🏽‍♂️", true); + emojiTrie.put("🧛🏽‍♂", true); + emojiTrie.put("🧛🏾‍♂️", true); + emojiTrie.put("🧛🏾‍♂", true); + emojiTrie.put("🧛🏿‍♂️", true); + emojiTrie.put("🧛🏿‍♂", true); + emojiTrie.put("🧛‍♀️", true); + emojiTrie.put("🧛‍♀", true); + emojiTrie.put("🧛🏻‍♀️", true); + emojiTrie.put("🧛🏻‍♀", true); + emojiTrie.put("🧛🏼‍♀️", true); + emojiTrie.put("🧛🏼‍♀", true); + emojiTrie.put("🧛🏽‍♀️", true); + emojiTrie.put("🧛🏽‍♀", true); + emojiTrie.put("🧛🏾‍♀️", true); + emojiTrie.put("🧛🏾‍♀", true); + emojiTrie.put("🧛🏿‍♀️", true); + emojiTrie.put("🧛🏿‍♀", true); + emojiTrie.put("🧜", true); + emojiTrie.put("🧜🏻", true); + emojiTrie.put("🧜🏼", true); + emojiTrie.put("🧜🏽", true); + emojiTrie.put("🧜🏾", true); + emojiTrie.put("🧜🏿", true); + emojiTrie.put("🧜‍♂️", true); + emojiTrie.put("🧜‍♂", true); + emojiTrie.put("🧜🏻‍♂️", true); + emojiTrie.put("🧜🏻‍♂", true); + emojiTrie.put("🧜🏼‍♂️", true); + emojiTrie.put("🧜🏼‍♂", true); + emojiTrie.put("🧜🏽‍♂️", true); + emojiTrie.put("🧜🏽‍♂", true); + emojiTrie.put("🧜🏾‍♂️", true); + emojiTrie.put("🧜🏾‍♂", true); + emojiTrie.put("🧜🏿‍♂️", true); + emojiTrie.put("🧜🏿‍♂", true); + emojiTrie.put("🧜‍♀️", true); + emojiTrie.put("🧜‍♀", true); + emojiTrie.put("🧜🏻‍♀️", true); + emojiTrie.put("🧜🏻‍♀", true); + emojiTrie.put("🧜🏼‍♀️", true); + emojiTrie.put("🧜🏼‍♀", true); + emojiTrie.put("🧜🏽‍♀️", true); + emojiTrie.put("🧜🏽‍♀", true); + emojiTrie.put("🧜🏾‍♀️", true); + emojiTrie.put("🧜🏾‍♀", true); + emojiTrie.put("🧜🏿‍♀️", true); + emojiTrie.put("🧜🏿‍♀", true); + emojiTrie.put("🧝", true); + emojiTrie.put("🧝🏻", true); + emojiTrie.put("🧝🏼", true); + emojiTrie.put("🧝🏽", true); + emojiTrie.put("🧝🏾", true); + emojiTrie.put("🧝🏿", true); + emojiTrie.put("🧝‍♂️", true); + emojiTrie.put("🧝‍♂", true); + emojiTrie.put("🧝🏻‍♂️", true); + emojiTrie.put("🧝🏻‍♂", true); + emojiTrie.put("🧝🏼‍♂️", true); + emojiTrie.put("🧝🏼‍♂", true); + emojiTrie.put("🧝🏽‍♂️", true); + emojiTrie.put("🧝🏽‍♂", true); + emojiTrie.put("🧝🏾‍♂️", true); + emojiTrie.put("🧝🏾‍♂", true); + emojiTrie.put("🧝🏿‍♂️", true); + emojiTrie.put("🧝🏿‍♂", true); + emojiTrie.put("🧝‍♀️", true); + emojiTrie.put("🧝‍♀", true); + emojiTrie.put("🧝🏻‍♀️", true); + emojiTrie.put("🧝🏻‍♀", true); + emojiTrie.put("🧝🏼‍♀️", true); + emojiTrie.put("🧝🏼‍♀", true); + emojiTrie.put("🧝🏽‍♀️", true); + emojiTrie.put("🧝🏽‍♀", true); + emojiTrie.put("🧝🏾‍♀️", true); + emojiTrie.put("🧝🏾‍♀", true); + emojiTrie.put("🧝🏿‍♀️", true); + emojiTrie.put("🧝🏿‍♀", true); + emojiTrie.put("🧞", true); + emojiTrie.put("🧞‍♂️", true); + emojiTrie.put("🧞‍♂", true); + emojiTrie.put("🧞‍♀️", true); + emojiTrie.put("🧞‍♀", true); + emojiTrie.put("🧟", true); + emojiTrie.put("🧟‍♂️", true); + emojiTrie.put("🧟‍♂", true); + emojiTrie.put("🧟‍♀️", true); + emojiTrie.put("🧟‍♀", true); + emojiTrie.put("🧌", true); + emojiTrie.put("💆", true); + emojiTrie.put("💆🏻", true); + emojiTrie.put("💆🏼", true); + emojiTrie.put("💆🏽", true); + emojiTrie.put("💆🏾", true); + emojiTrie.put("💆🏿", true); + emojiTrie.put("💆‍♂️", true); + emojiTrie.put("💆‍♂", true); + emojiTrie.put("💆🏻‍♂️", true); + emojiTrie.put("💆🏻‍♂", true); + emojiTrie.put("💆🏼‍♂️", true); + emojiTrie.put("💆🏼‍♂", true); + emojiTrie.put("💆🏽‍♂️", true); + emojiTrie.put("💆🏽‍♂", true); + emojiTrie.put("💆🏾‍♂️", true); + emojiTrie.put("💆🏾‍♂", true); + emojiTrie.put("💆🏿‍♂️", true); + emojiTrie.put("💆🏿‍♂", true); + emojiTrie.put("💆‍♀️", true); + emojiTrie.put("💆‍♀", true); + emojiTrie.put("💆🏻‍♀️", true); + emojiTrie.put("💆🏻‍♀", true); + emojiTrie.put("💆🏼‍♀️", true); + emojiTrie.put("💆🏼‍♀", true); + emojiTrie.put("💆🏽‍♀️", true); + emojiTrie.put("💆🏽‍♀", true); + emojiTrie.put("💆🏾‍♀️", true); + emojiTrie.put("💆🏾‍♀", true); + emojiTrie.put("💆🏿‍♀️", true); + emojiTrie.put("💆🏿‍♀", true); + emojiTrie.put("💇", true); + emojiTrie.put("💇🏻", true); + emojiTrie.put("💇🏼", true); + emojiTrie.put("💇🏽", true); + emojiTrie.put("💇🏾", true); + emojiTrie.put("💇🏿", true); + emojiTrie.put("💇‍♂️", true); + emojiTrie.put("💇‍♂", true); + emojiTrie.put("💇🏻‍♂️", true); + emojiTrie.put("💇🏻‍♂", true); + emojiTrie.put("💇🏼‍♂️", true); + emojiTrie.put("💇🏼‍♂", true); + emojiTrie.put("💇🏽‍♂️", true); + emojiTrie.put("💇🏽‍♂", true); + emojiTrie.put("💇🏾‍♂️", true); + emojiTrie.put("💇🏾‍♂", true); + emojiTrie.put("💇🏿‍♂️", true); + emojiTrie.put("💇🏿‍♂", true); + emojiTrie.put("💇‍♀️", true); + emojiTrie.put("💇‍♀", true); + emojiTrie.put("💇🏻‍♀️", true); + emojiTrie.put("💇🏻‍♀", true); + emojiTrie.put("💇🏼‍♀️", true); + emojiTrie.put("💇🏼‍♀", true); + emojiTrie.put("💇🏽‍♀️", true); + emojiTrie.put("💇🏽‍♀", true); + emojiTrie.put("💇🏾‍♀️", true); + emojiTrie.put("💇🏾‍♀", true); + emojiTrie.put("💇🏿‍♀️", true); + emojiTrie.put("💇🏿‍♀", true); + emojiTrie.put("🚶", true); + emojiTrie.put("🚶🏻", true); + emojiTrie.put("🚶🏼", true); + emojiTrie.put("🚶🏽", true); + emojiTrie.put("🚶🏾", true); + emojiTrie.put("🚶🏿", true); + emojiTrie.put("🚶‍♂️", true); + emojiTrie.put("🚶‍♂", true); + emojiTrie.put("🚶🏻‍♂️", true); + emojiTrie.put("🚶🏻‍♂", true); + emojiTrie.put("🚶🏼‍♂️", true); + emojiTrie.put("🚶🏼‍♂", true); + emojiTrie.put("🚶🏽‍♂️", true); + emojiTrie.put("🚶🏽‍♂", true); + emojiTrie.put("🚶🏾‍♂️", true); + emojiTrie.put("🚶🏾‍♂", true); + emojiTrie.put("🚶🏿‍♂️", true); + emojiTrie.put("🚶🏿‍♂", true); + emojiTrie.put("🚶‍♀️", true); + emojiTrie.put("🚶‍♀", true); + emojiTrie.put("🚶🏻‍♀️", true); + emojiTrie.put("🚶🏻‍♀", true); + emojiTrie.put("🚶🏼‍♀️", true); + emojiTrie.put("🚶🏼‍♀", true); + emojiTrie.put("🚶🏽‍♀️", true); + emojiTrie.put("🚶🏽‍♀", true); + emojiTrie.put("🚶🏾‍♀️", true); + emojiTrie.put("🚶🏾‍♀", true); + emojiTrie.put("🚶🏿‍♀️", true); + emojiTrie.put("🚶🏿‍♀", true); + emojiTrie.put("🧍", true); + emojiTrie.put("🧍🏻", true); + emojiTrie.put("🧍🏼", true); + emojiTrie.put("🧍🏽", true); + emojiTrie.put("🧍🏾", true); + emojiTrie.put("🧍🏿", true); + emojiTrie.put("🧍‍♂️", true); + emojiTrie.put("🧍‍♂", true); + emojiTrie.put("🧍🏻‍♂️", true); + emojiTrie.put("🧍🏻‍♂", true); + emojiTrie.put("🧍🏼‍♂️", true); + emojiTrie.put("🧍🏼‍♂", true); + emojiTrie.put("🧍🏽‍♂️", true); + emojiTrie.put("🧍🏽‍♂", true); + emojiTrie.put("🧍🏾‍♂️", true); + emojiTrie.put("🧍🏾‍♂", true); + emojiTrie.put("🧍🏿‍♂️", true); + emojiTrie.put("🧍🏿‍♂", true); + emojiTrie.put("🧍‍♀️", true); + emojiTrie.put("🧍‍♀", true); + emojiTrie.put("🧍🏻‍♀️", true); + emojiTrie.put("🧍🏻‍♀", true); + emojiTrie.put("🧍🏼‍♀️", true); + emojiTrie.put("🧍🏼‍♀", true); + emojiTrie.put("🧍🏽‍♀️", true); + emojiTrie.put("🧍🏽‍♀", true); + emojiTrie.put("🧍🏾‍♀️", true); + emojiTrie.put("🧍🏾‍♀", true); + emojiTrie.put("🧍🏿‍♀️", true); + emojiTrie.put("🧍🏿‍♀", true); + emojiTrie.put("🧎", true); + emojiTrie.put("🧎🏻", true); + emojiTrie.put("🧎🏼", true); + emojiTrie.put("🧎🏽", true); + emojiTrie.put("🧎🏾", true); + emojiTrie.put("🧎🏿", true); + emojiTrie.put("🧎‍♂️", true); + emojiTrie.put("🧎‍♂", true); + emojiTrie.put("🧎🏻‍♂️", true); + emojiTrie.put("🧎🏻‍♂", true); + emojiTrie.put("🧎🏼‍♂️", true); + emojiTrie.put("🧎🏼‍♂", true); + emojiTrie.put("🧎🏽‍♂️", true); + emojiTrie.put("🧎🏽‍♂", true); + emojiTrie.put("🧎🏾‍♂️", true); + emojiTrie.put("🧎🏾‍♂", true); + emojiTrie.put("🧎🏿‍♂️", true); + emojiTrie.put("🧎🏿‍♂", true); + emojiTrie.put("🧎‍♀️", true); + emojiTrie.put("🧎‍♀", true); + emojiTrie.put("🧎🏻‍♀️", true); + emojiTrie.put("🧎🏻‍♀", true); + emojiTrie.put("🧎🏼‍♀️", true); + emojiTrie.put("🧎🏼‍♀", true); + emojiTrie.put("🧎🏽‍♀️", true); + emojiTrie.put("🧎🏽‍♀", true); + emojiTrie.put("🧎🏾‍♀️", true); + emojiTrie.put("🧎🏾‍♀", true); + emojiTrie.put("🧎🏿‍♀️", true); + emojiTrie.put("🧎🏿‍♀", true); + emojiTrie.put("🧑‍🦯", true); + emojiTrie.put("🧑🏻‍🦯", true); + emojiTrie.put("🧑🏼‍🦯", true); + emojiTrie.put("🧑🏽‍🦯", true); + emojiTrie.put("🧑🏾‍🦯", true); + emojiTrie.put("🧑🏿‍🦯", true); + emojiTrie.put("👨‍🦯", true); + emojiTrie.put("👨🏻‍🦯", true); + emojiTrie.put("👨🏼‍🦯", true); + emojiTrie.put("👨🏽‍🦯", true); + emojiTrie.put("👨🏾‍🦯", true); + emojiTrie.put("👨🏿‍🦯", true); + emojiTrie.put("👩‍🦯", true); + emojiTrie.put("👩🏻‍🦯", true); + emojiTrie.put("👩🏼‍🦯", true); + emojiTrie.put("👩🏽‍🦯", true); + emojiTrie.put("👩🏾‍🦯", true); + emojiTrie.put("👩🏿‍🦯", true); + emojiTrie.put("🧑‍🦼", true); + emojiTrie.put("🧑🏻‍🦼", true); + emojiTrie.put("🧑🏼‍🦼", true); + emojiTrie.put("🧑🏽‍🦼", true); + emojiTrie.put("🧑🏾‍🦼", true); + emojiTrie.put("🧑🏿‍🦼", true); + emojiTrie.put("👨‍🦼", true); + emojiTrie.put("👨🏻‍🦼", true); + emojiTrie.put("👨🏼‍🦼", true); + emojiTrie.put("👨🏽‍🦼", true); + emojiTrie.put("👨🏾‍🦼", true); + emojiTrie.put("👨🏿‍🦼", true); + emojiTrie.put("👩‍🦼", true); + emojiTrie.put("👩🏻‍🦼", true); + emojiTrie.put("👩🏼‍🦼", true); + emojiTrie.put("👩🏽‍🦼", true); + emojiTrie.put("👩🏾‍🦼", true); + emojiTrie.put("👩🏿‍🦼", true); + emojiTrie.put("🧑‍🦽", true); + emojiTrie.put("🧑🏻‍🦽", true); + emojiTrie.put("🧑🏼‍🦽", true); + emojiTrie.put("🧑🏽‍🦽", true); + emojiTrie.put("🧑🏾‍🦽", true); + emojiTrie.put("🧑🏿‍🦽", true); + emojiTrie.put("👨‍🦽", true); + emojiTrie.put("👨🏻‍🦽", true); + emojiTrie.put("👨🏼‍🦽", true); + emojiTrie.put("👨🏽‍🦽", true); + emojiTrie.put("👨🏾‍🦽", true); + emojiTrie.put("👨🏿‍🦽", true); + emojiTrie.put("👩‍🦽", true); + emojiTrie.put("👩🏻‍🦽", true); + emojiTrie.put("👩🏼‍🦽", true); + emojiTrie.put("👩🏽‍🦽", true); + emojiTrie.put("👩🏾‍🦽", true); + emojiTrie.put("👩🏿‍🦽", true); + emojiTrie.put("🏃", true); + emojiTrie.put("🏃🏻", true); + emojiTrie.put("🏃🏼", true); + emojiTrie.put("🏃🏽", true); + emojiTrie.put("🏃🏾", true); + emojiTrie.put("🏃🏿", true); + emojiTrie.put("🏃‍♂️", true); + emojiTrie.put("🏃‍♂", true); + emojiTrie.put("🏃🏻‍♂️", true); + emojiTrie.put("🏃🏻‍♂", true); + emojiTrie.put("🏃🏼‍♂️", true); + emojiTrie.put("🏃🏼‍♂", true); + emojiTrie.put("🏃🏽‍♂️", true); + emojiTrie.put("🏃🏽‍♂", true); + emojiTrie.put("🏃🏾‍♂️", true); + emojiTrie.put("🏃🏾‍♂", true); + emojiTrie.put("🏃🏿‍♂️", true); + emojiTrie.put("🏃🏿‍♂", true); + emojiTrie.put("🏃‍♀️", true); + emojiTrie.put("🏃‍♀", true); + emojiTrie.put("🏃🏻‍♀️", true); + emojiTrie.put("🏃🏻‍♀", true); + emojiTrie.put("🏃🏼‍♀️", true); + emojiTrie.put("🏃🏼‍♀", true); + emojiTrie.put("🏃🏽‍♀️", true); + emojiTrie.put("🏃🏽‍♀", true); + emojiTrie.put("🏃🏾‍♀️", true); + emojiTrie.put("🏃🏾‍♀", true); + emojiTrie.put("🏃🏿‍♀️", true); + emojiTrie.put("🏃🏿‍♀", true); + emojiTrie.put("💃", true); + emojiTrie.put("💃🏻", true); + emojiTrie.put("💃🏼", true); + emojiTrie.put("💃🏽", true); + emojiTrie.put("💃🏾", true); + emojiTrie.put("💃🏿", true); + emojiTrie.put("🕺", true); + emojiTrie.put("🕺🏻", true); + emojiTrie.put("🕺🏼", true); + emojiTrie.put("🕺🏽", true); + emojiTrie.put("🕺🏾", true); + emojiTrie.put("🕺🏿", true); + emojiTrie.put("🕴️", true); + emojiTrie.put("🕴", true); + emojiTrie.put("🕴🏻", true); + emojiTrie.put("🕴🏼", true); + emojiTrie.put("🕴🏽", true); + emojiTrie.put("🕴🏾", true); + emojiTrie.put("🕴🏿", true); + emojiTrie.put("👯", true); + emojiTrie.put("👯‍♂️", true); + emojiTrie.put("👯‍♂", true); + emojiTrie.put("👯‍♀️", true); + emojiTrie.put("👯‍♀", true); + emojiTrie.put("🧖", true); + emojiTrie.put("🧖🏻", true); + emojiTrie.put("🧖🏼", true); + emojiTrie.put("🧖🏽", true); + emojiTrie.put("🧖🏾", true); + emojiTrie.put("🧖🏿", true); + emojiTrie.put("🧖‍♂️", true); + emojiTrie.put("🧖‍♂", true); + emojiTrie.put("🧖🏻‍♂️", true); + emojiTrie.put("🧖🏻‍♂", true); + emojiTrie.put("🧖🏼‍♂️", true); + emojiTrie.put("🧖🏼‍♂", true); + emojiTrie.put("🧖🏽‍♂️", true); + emojiTrie.put("🧖🏽‍♂", true); + emojiTrie.put("🧖🏾‍♂️", true); + emojiTrie.put("🧖🏾‍♂", true); + emojiTrie.put("🧖🏿‍♂️", true); + emojiTrie.put("🧖🏿‍♂", true); + emojiTrie.put("🧖‍♀️", true); + emojiTrie.put("🧖‍♀", true); + emojiTrie.put("🧖🏻‍♀️", true); + emojiTrie.put("🧖🏻‍♀", true); + emojiTrie.put("🧖🏼‍♀️", true); + emojiTrie.put("🧖🏼‍♀", true); + emojiTrie.put("🧖🏽‍♀️", true); + emojiTrie.put("🧖🏽‍♀", true); + emojiTrie.put("🧖🏾‍♀️", true); + emojiTrie.put("🧖🏾‍♀", true); + emojiTrie.put("🧖🏿‍♀️", true); + emojiTrie.put("🧖🏿‍♀", true); + emojiTrie.put("🧗", true); + emojiTrie.put("🧗🏻", true); + emojiTrie.put("🧗🏼", true); + emojiTrie.put("🧗🏽", true); + emojiTrie.put("🧗🏾", true); + emojiTrie.put("🧗🏿", true); + emojiTrie.put("🧗‍♂️", true); + emojiTrie.put("🧗‍♂", true); + emojiTrie.put("🧗🏻‍♂️", true); + emojiTrie.put("🧗🏻‍♂", true); + emojiTrie.put("🧗🏼‍♂️", true); + emojiTrie.put("🧗🏼‍♂", true); + emojiTrie.put("🧗🏽‍♂️", true); + emojiTrie.put("🧗🏽‍♂", true); + emojiTrie.put("🧗🏾‍♂️", true); + emojiTrie.put("🧗🏾‍♂", true); + emojiTrie.put("🧗🏿‍♂️", true); + emojiTrie.put("🧗🏿‍♂", true); + emojiTrie.put("🧗‍♀️", true); + emojiTrie.put("🧗‍♀", true); + emojiTrie.put("🧗🏻‍♀️", true); + emojiTrie.put("🧗🏻‍♀", true); + emojiTrie.put("🧗🏼‍♀️", true); + emojiTrie.put("🧗🏼‍♀", true); + emojiTrie.put("🧗🏽‍♀️", true); + emojiTrie.put("🧗🏽‍♀", true); + emojiTrie.put("🧗🏾‍♀️", true); + emojiTrie.put("🧗🏾‍♀", true); + emojiTrie.put("🧗🏿‍♀️", true); + emojiTrie.put("🧗🏿‍♀", true); + emojiTrie.put("🤺", true); + emojiTrie.put("🏇", true); + emojiTrie.put("🏇🏻", true); + emojiTrie.put("🏇🏼", true); + emojiTrie.put("🏇🏽", true); + emojiTrie.put("🏇🏾", true); + emojiTrie.put("🏇🏿", true); + emojiTrie.put("⛷️", true); + emojiTrie.put("⛷", true); + emojiTrie.put("🏂", true); + emojiTrie.put("🏂🏻", true); + emojiTrie.put("🏂🏼", true); + emojiTrie.put("🏂🏽", true); + emojiTrie.put("🏂🏾", true); + emojiTrie.put("🏂🏿", true); + emojiTrie.put("🏌️", true); + emojiTrie.put("🏌", true); + emojiTrie.put("🏌🏻", true); + emojiTrie.put("🏌🏼", true); + emojiTrie.put("🏌🏽", true); + emojiTrie.put("🏌🏾", true); + emojiTrie.put("🏌🏿", true); + emojiTrie.put("🏌️‍♂️", true); + emojiTrie.put("🏌‍♂️", true); + emojiTrie.put("🏌️‍♂", true); + emojiTrie.put("🏌‍♂", true); + emojiTrie.put("🏌🏻‍♂️", true); + emojiTrie.put("🏌🏻‍♂", true); + emojiTrie.put("🏌🏼‍♂️", true); + emojiTrie.put("🏌🏼‍♂", true); + emojiTrie.put("🏌🏽‍♂️", true); + emojiTrie.put("🏌🏽‍♂", true); + emojiTrie.put("🏌🏾‍♂️", true); + emojiTrie.put("🏌🏾‍♂", true); + emojiTrie.put("🏌🏿‍♂️", true); + emojiTrie.put("🏌🏿‍♂", true); + emojiTrie.put("🏌️‍♀️", true); + emojiTrie.put("🏌‍♀️", true); + emojiTrie.put("🏌️‍♀", true); + emojiTrie.put("🏌‍♀", true); + emojiTrie.put("🏌🏻‍♀️", true); + emojiTrie.put("🏌🏻‍♀", true); + emojiTrie.put("🏌🏼‍♀️", true); + emojiTrie.put("🏌🏼‍♀", true); + emojiTrie.put("🏌🏽‍♀️", true); + emojiTrie.put("🏌🏽‍♀", true); + emojiTrie.put("🏌🏾‍♀️", true); + emojiTrie.put("🏌🏾‍♀", true); + emojiTrie.put("🏌🏿‍♀️", true); + emojiTrie.put("🏌🏿‍♀", true); + emojiTrie.put("🏄", true); + emojiTrie.put("🏄🏻", true); + emojiTrie.put("🏄🏼", true); + emojiTrie.put("🏄🏽", true); + emojiTrie.put("🏄🏾", true); + emojiTrie.put("🏄🏿", true); + emojiTrie.put("🏄‍♂️", true); + emojiTrie.put("🏄‍♂", true); + emojiTrie.put("🏄🏻‍♂️", true); + emojiTrie.put("🏄🏻‍♂", true); + emojiTrie.put("🏄🏼‍♂️", true); + emojiTrie.put("🏄🏼‍♂", true); + emojiTrie.put("🏄🏽‍♂️", true); + emojiTrie.put("🏄🏽‍♂", true); + emojiTrie.put("🏄🏾‍♂️", true); + emojiTrie.put("🏄🏾‍♂", true); + emojiTrie.put("🏄🏿‍♂️", true); + emojiTrie.put("🏄🏿‍♂", true); + emojiTrie.put("🏄‍♀️", true); + emojiTrie.put("🏄‍♀", true); + emojiTrie.put("🏄🏻‍♀️", true); + emojiTrie.put("🏄🏻‍♀", true); + emojiTrie.put("🏄🏼‍♀️", true); + emojiTrie.put("🏄🏼‍♀", true); + emojiTrie.put("🏄🏽‍♀️", true); + emojiTrie.put("🏄🏽‍♀", true); + emojiTrie.put("🏄🏾‍♀️", true); + emojiTrie.put("🏄🏾‍♀", true); + emojiTrie.put("🏄🏿‍♀️", true); + emojiTrie.put("🏄🏿‍♀", true); + emojiTrie.put("🚣", true); + emojiTrie.put("🚣🏻", true); + emojiTrie.put("🚣🏼", true); + emojiTrie.put("🚣🏽", true); + emojiTrie.put("🚣🏾", true); + emojiTrie.put("🚣🏿", true); + emojiTrie.put("🚣‍♂️", true); + emojiTrie.put("🚣‍♂", true); + emojiTrie.put("🚣🏻‍♂️", true); + emojiTrie.put("🚣🏻‍♂", true); + emojiTrie.put("🚣🏼‍♂️", true); + emojiTrie.put("🚣🏼‍♂", true); + emojiTrie.put("🚣🏽‍♂️", true); + emojiTrie.put("🚣🏽‍♂", true); + emojiTrie.put("🚣🏾‍♂️", true); + emojiTrie.put("🚣🏾‍♂", true); + emojiTrie.put("🚣🏿‍♂️", true); + emojiTrie.put("🚣🏿‍♂", true); + emojiTrie.put("🚣‍♀️", true); + emojiTrie.put("🚣‍♀", true); + emojiTrie.put("🚣🏻‍♀️", true); + emojiTrie.put("🚣🏻‍♀", true); + emojiTrie.put("🚣🏼‍♀️", true); + emojiTrie.put("🚣🏼‍♀", true); + emojiTrie.put("🚣🏽‍♀️", true); + emojiTrie.put("🚣🏽‍♀", true); + emojiTrie.put("🚣🏾‍♀️", true); + emojiTrie.put("🚣🏾‍♀", true); + emojiTrie.put("🚣🏿‍♀️", true); + emojiTrie.put("🚣🏿‍♀", true); + emojiTrie.put("🏊", true); + emojiTrie.put("🏊🏻", true); + emojiTrie.put("🏊🏼", true); + emojiTrie.put("🏊🏽", true); + emojiTrie.put("🏊🏾", true); + emojiTrie.put("🏊🏿", true); + emojiTrie.put("🏊‍♂️", true); + emojiTrie.put("🏊‍♂", true); + emojiTrie.put("🏊🏻‍♂️", true); + emojiTrie.put("🏊🏻‍♂", true); + emojiTrie.put("🏊🏼‍♂️", true); + emojiTrie.put("🏊🏼‍♂", true); + emojiTrie.put("🏊🏽‍♂️", true); + emojiTrie.put("🏊🏽‍♂", true); + emojiTrie.put("🏊🏾‍♂️", true); + emojiTrie.put("🏊🏾‍♂", true); + emojiTrie.put("🏊🏿‍♂️", true); + emojiTrie.put("🏊🏿‍♂", true); + emojiTrie.put("🏊‍♀️", true); + emojiTrie.put("🏊‍♀", true); + emojiTrie.put("🏊🏻‍♀️", true); + emojiTrie.put("🏊🏻‍♀", true); + emojiTrie.put("🏊🏼‍♀️", true); + emojiTrie.put("🏊🏼‍♀", true); + emojiTrie.put("🏊🏽‍♀️", true); + emojiTrie.put("🏊🏽‍♀", true); + emojiTrie.put("🏊🏾‍♀️", true); + emojiTrie.put("🏊🏾‍♀", true); + emojiTrie.put("🏊🏿‍♀️", true); + emojiTrie.put("🏊🏿‍♀", true); + emojiTrie.put("⛹️", true); + emojiTrie.put("⛹", true); + emojiTrie.put("⛹🏻", true); + emojiTrie.put("⛹🏼", true); + emojiTrie.put("⛹🏽", true); + emojiTrie.put("⛹🏾", true); + emojiTrie.put("⛹🏿", true); + emojiTrie.put("⛹️‍♂️", true); + emojiTrie.put("⛹‍♂️", true); + emojiTrie.put("⛹️‍♂", true); + emojiTrie.put("⛹‍♂", true); + emojiTrie.put("⛹🏻‍♂️", true); + emojiTrie.put("⛹🏻‍♂", true); + emojiTrie.put("⛹🏼‍♂️", true); + emojiTrie.put("⛹🏼‍♂", true); + emojiTrie.put("⛹🏽‍♂️", true); + emojiTrie.put("⛹🏽‍♂", true); + emojiTrie.put("⛹🏾‍♂️", true); + emojiTrie.put("⛹🏾‍♂", true); + emojiTrie.put("⛹🏿‍♂️", true); + emojiTrie.put("⛹🏿‍♂", true); + emojiTrie.put("⛹️‍♀️", true); + emojiTrie.put("⛹‍♀️", true); + emojiTrie.put("⛹️‍♀", true); + emojiTrie.put("⛹‍♀", true); + emojiTrie.put("⛹🏻‍♀️", true); + emojiTrie.put("⛹🏻‍♀", true); + emojiTrie.put("⛹🏼‍♀️", true); + emojiTrie.put("⛹🏼‍♀", true); + emojiTrie.put("⛹🏽‍♀️", true); + emojiTrie.put("⛹🏽‍♀", true); + emojiTrie.put("⛹🏾‍♀️", true); + emojiTrie.put("⛹🏾‍♀", true); + emojiTrie.put("⛹🏿‍♀️", true); + emojiTrie.put("⛹🏿‍♀", true); + emojiTrie.put("🏋️", true); + emojiTrie.put("🏋", true); + emojiTrie.put("🏋🏻", true); + emojiTrie.put("🏋🏼", true); + emojiTrie.put("🏋🏽", true); + emojiTrie.put("🏋🏾", true); + emojiTrie.put("🏋🏿", true); + emojiTrie.put("🏋️‍♂️", true); + emojiTrie.put("🏋‍♂️", true); + emojiTrie.put("🏋️‍♂", true); + emojiTrie.put("🏋‍♂", true); + emojiTrie.put("🏋🏻‍♂️", true); + emojiTrie.put("🏋🏻‍♂", true); + emojiTrie.put("🏋🏼‍♂️", true); + emojiTrie.put("🏋🏼‍♂", true); + emojiTrie.put("🏋🏽‍♂️", true); + emojiTrie.put("🏋🏽‍♂", true); + emojiTrie.put("🏋🏾‍♂️", true); + emojiTrie.put("🏋🏾‍♂", true); + emojiTrie.put("🏋🏿‍♂️", true); + emojiTrie.put("🏋🏿‍♂", true); + emojiTrie.put("🏋️‍♀️", true); + emojiTrie.put("🏋‍♀️", true); + emojiTrie.put("🏋️‍♀", true); + emojiTrie.put("🏋‍♀", true); + emojiTrie.put("🏋🏻‍♀️", true); + emojiTrie.put("🏋🏻‍♀", true); + emojiTrie.put("🏋🏼‍♀️", true); + emojiTrie.put("🏋🏼‍♀", true); + emojiTrie.put("🏋🏽‍♀️", true); + emojiTrie.put("🏋🏽‍♀", true); + emojiTrie.put("🏋🏾‍♀️", true); + emojiTrie.put("🏋🏾‍♀", true); + emojiTrie.put("🏋🏿‍♀️", true); + emojiTrie.put("🏋🏿‍♀", true); + emojiTrie.put("🚴", true); + emojiTrie.put("🚴🏻", true); + emojiTrie.put("🚴🏼", true); + emojiTrie.put("🚴🏽", true); + emojiTrie.put("🚴🏾", true); + emojiTrie.put("🚴🏿", true); + emojiTrie.put("🚴‍♂️", true); + emojiTrie.put("🚴‍♂", true); + emojiTrie.put("🚴🏻‍♂️", true); + emojiTrie.put("🚴🏻‍♂", true); + emojiTrie.put("🚴🏼‍♂️", true); + emojiTrie.put("🚴🏼‍♂", true); + emojiTrie.put("🚴🏽‍♂️", true); + emojiTrie.put("🚴🏽‍♂", true); + emojiTrie.put("🚴🏾‍♂️", true); + emojiTrie.put("🚴🏾‍♂", true); + emojiTrie.put("🚴🏿‍♂️", true); + emojiTrie.put("🚴🏿‍♂", true); + emojiTrie.put("🚴‍♀️", true); + emojiTrie.put("🚴‍♀", true); + emojiTrie.put("🚴🏻‍♀️", true); + emojiTrie.put("🚴🏻‍♀", true); + emojiTrie.put("🚴🏼‍♀️", true); + emojiTrie.put("🚴🏼‍♀", true); + emojiTrie.put("🚴🏽‍♀️", true); + emojiTrie.put("🚴🏽‍♀", true); + emojiTrie.put("🚴🏾‍♀️", true); + emojiTrie.put("🚴🏾‍♀", true); + emojiTrie.put("🚴🏿‍♀️", true); + emojiTrie.put("🚴🏿‍♀", true); + emojiTrie.put("🚵", true); + emojiTrie.put("🚵🏻", true); + emojiTrie.put("🚵🏼", true); + emojiTrie.put("🚵🏽", true); + emojiTrie.put("🚵🏾", true); + emojiTrie.put("🚵🏿", true); + emojiTrie.put("🚵‍♂️", true); + emojiTrie.put("🚵‍♂", true); + emojiTrie.put("🚵🏻‍♂️", true); + emojiTrie.put("🚵🏻‍♂", true); + emojiTrie.put("🚵🏼‍♂️", true); + emojiTrie.put("🚵🏼‍♂", true); + emojiTrie.put("🚵🏽‍♂️", true); + emojiTrie.put("🚵🏽‍♂", true); + emojiTrie.put("🚵🏾‍♂️", true); + emojiTrie.put("🚵🏾‍♂", true); + emojiTrie.put("🚵🏿‍♂️", true); + emojiTrie.put("🚵🏿‍♂", true); + emojiTrie.put("🚵‍♀️", true); + emojiTrie.put("🚵‍♀", true); + emojiTrie.put("🚵🏻‍♀️", true); + emojiTrie.put("🚵🏻‍♀", true); + emojiTrie.put("🚵🏼‍♀️", true); + emojiTrie.put("🚵🏼‍♀", true); + emojiTrie.put("🚵🏽‍♀️", true); + emojiTrie.put("🚵🏽‍♀", true); + emojiTrie.put("🚵🏾‍♀️", true); + emojiTrie.put("🚵🏾‍♀", true); + emojiTrie.put("🚵🏿‍♀️", true); + emojiTrie.put("🚵🏿‍♀", true); + emojiTrie.put("🤸", true); + emojiTrie.put("🤸🏻", true); + emojiTrie.put("🤸🏼", true); + emojiTrie.put("🤸🏽", true); + emojiTrie.put("🤸🏾", true); + emojiTrie.put("🤸🏿", true); + emojiTrie.put("🤸‍♂️", true); + emojiTrie.put("🤸‍♂", true); + emojiTrie.put("🤸🏻‍♂️", true); + emojiTrie.put("🤸🏻‍♂", true); + emojiTrie.put("🤸🏼‍♂️", true); + emojiTrie.put("🤸🏼‍♂", true); + emojiTrie.put("🤸🏽‍♂️", true); + emojiTrie.put("🤸🏽‍♂", true); + emojiTrie.put("🤸🏾‍♂️", true); + emojiTrie.put("🤸🏾‍♂", true); + emojiTrie.put("🤸🏿‍♂️", true); + emojiTrie.put("🤸🏿‍♂", true); + emojiTrie.put("🤸‍♀️", true); + emojiTrie.put("🤸‍♀", true); + emojiTrie.put("🤸🏻‍♀️", true); + emojiTrie.put("🤸🏻‍♀", true); + emojiTrie.put("🤸🏼‍♀️", true); + emojiTrie.put("🤸🏼‍♀", true); + emojiTrie.put("🤸🏽‍♀️", true); + emojiTrie.put("🤸🏽‍♀", true); + emojiTrie.put("🤸🏾‍♀️", true); + emojiTrie.put("🤸🏾‍♀", true); + emojiTrie.put("🤸🏿‍♀️", true); + emojiTrie.put("🤸🏿‍♀", true); + emojiTrie.put("🤼", true); + emojiTrie.put("🤼‍♂️", true); + emojiTrie.put("🤼‍♂", true); + emojiTrie.put("🤼‍♀️", true); + emojiTrie.put("🤼‍♀", true); + emojiTrie.put("🤽", true); + emojiTrie.put("🤽🏻", true); + emojiTrie.put("🤽🏼", true); + emojiTrie.put("🤽🏽", true); + emojiTrie.put("🤽🏾", true); + emojiTrie.put("🤽🏿", true); + emojiTrie.put("🤽‍♂️", true); + emojiTrie.put("🤽‍♂", true); + emojiTrie.put("🤽🏻‍♂️", true); + emojiTrie.put("🤽🏻‍♂", true); + emojiTrie.put("🤽🏼‍♂️", true); + emojiTrie.put("🤽🏼‍♂", true); + emojiTrie.put("🤽🏽‍♂️", true); + emojiTrie.put("🤽🏽‍♂", true); + emojiTrie.put("🤽🏾‍♂️", true); + emojiTrie.put("🤽🏾‍♂", true); + emojiTrie.put("🤽🏿‍♂️", true); + emojiTrie.put("🤽🏿‍♂", true); + emojiTrie.put("🤽‍♀️", true); + emojiTrie.put("🤽‍♀", true); + emojiTrie.put("🤽🏻‍♀️", true); + emojiTrie.put("🤽🏻‍♀", true); + emojiTrie.put("🤽🏼‍♀️", true); + emojiTrie.put("🤽🏼‍♀", true); + emojiTrie.put("🤽🏽‍♀️", true); + emojiTrie.put("🤽🏽‍♀", true); + emojiTrie.put("🤽🏾‍♀️", true); + emojiTrie.put("🤽🏾‍♀", true); + emojiTrie.put("🤽🏿‍♀️", true); + emojiTrie.put("🤽🏿‍♀", true); + emojiTrie.put("🤾", true); + emojiTrie.put("🤾🏻", true); + emojiTrie.put("🤾🏼", true); + emojiTrie.put("🤾🏽", true); + emojiTrie.put("🤾🏾", true); + emojiTrie.put("🤾🏿", true); + emojiTrie.put("🤾‍♂️", true); + emojiTrie.put("🤾‍♂", true); + emojiTrie.put("🤾🏻‍♂️", true); + emojiTrie.put("🤾🏻‍♂", true); + emojiTrie.put("🤾🏼‍♂️", true); + emojiTrie.put("🤾🏼‍♂", true); + emojiTrie.put("🤾🏽‍♂️", true); + emojiTrie.put("🤾🏽‍♂", true); + emojiTrie.put("🤾🏾‍♂️", true); + emojiTrie.put("🤾🏾‍♂", true); + emojiTrie.put("🤾🏿‍♂️", true); + emojiTrie.put("🤾🏿‍♂", true); + emojiTrie.put("🤾‍♀️", true); + emojiTrie.put("🤾‍♀", true); + emojiTrie.put("🤾🏻‍♀️", true); + emojiTrie.put("🤾🏻‍♀", true); + emojiTrie.put("🤾🏼‍♀️", true); + emojiTrie.put("🤾🏼‍♀", true); + emojiTrie.put("🤾🏽‍♀️", true); + emojiTrie.put("🤾🏽‍♀", true); + emojiTrie.put("🤾🏾‍♀️", true); + emojiTrie.put("🤾🏾‍♀", true); + emojiTrie.put("🤾🏿‍♀️", true); + emojiTrie.put("🤾🏿‍♀", true); + emojiTrie.put("🤹", true); + emojiTrie.put("🤹🏻", true); + emojiTrie.put("🤹🏼", true); + emojiTrie.put("🤹🏽", true); + emojiTrie.put("🤹🏾", true); + emojiTrie.put("🤹🏿", true); + emojiTrie.put("🤹‍♂️", true); + emojiTrie.put("🤹‍♂", true); + emojiTrie.put("🤹🏻‍♂️", true); + emojiTrie.put("🤹🏻‍♂", true); + emojiTrie.put("🤹🏼‍♂️", true); + emojiTrie.put("🤹🏼‍♂", true); + emojiTrie.put("🤹🏽‍♂️", true); + emojiTrie.put("🤹🏽‍♂", true); + emojiTrie.put("🤹🏾‍♂️", true); + emojiTrie.put("🤹🏾‍♂", true); + emojiTrie.put("🤹🏿‍♂️", true); + emojiTrie.put("🤹🏿‍♂", true); + emojiTrie.put("🤹‍♀️", true); + emojiTrie.put("🤹‍♀", true); + emojiTrie.put("🤹🏻‍♀️", true); + emojiTrie.put("🤹🏻‍♀", true); + emojiTrie.put("🤹🏼‍♀️", true); + emojiTrie.put("🤹🏼‍♀", true); + emojiTrie.put("🤹🏽‍♀️", true); + emojiTrie.put("🤹🏽‍♀", true); + emojiTrie.put("🤹🏾‍♀️", true); + emojiTrie.put("🤹🏾‍♀", true); + emojiTrie.put("🤹🏿‍♀️", true); + emojiTrie.put("🤹🏿‍♀", true); + emojiTrie.put("🧘", true); + emojiTrie.put("🧘🏻", true); + emojiTrie.put("🧘🏼", true); + emojiTrie.put("🧘🏽", true); + emojiTrie.put("🧘🏾", true); + emojiTrie.put("🧘🏿", true); + emojiTrie.put("🧘‍♂️", true); + emojiTrie.put("🧘‍♂", true); + emojiTrie.put("🧘🏻‍♂️", true); + emojiTrie.put("🧘🏻‍♂", true); + emojiTrie.put("🧘🏼‍♂️", true); + emojiTrie.put("🧘🏼‍♂", true); + emojiTrie.put("🧘🏽‍♂️", true); + emojiTrie.put("🧘🏽‍♂", true); + emojiTrie.put("🧘🏾‍♂️", true); + emojiTrie.put("🧘🏾‍♂", true); + emojiTrie.put("🧘🏿‍♂️", true); + emojiTrie.put("🧘🏿‍♂", true); + emojiTrie.put("🧘‍♀️", true); + emojiTrie.put("🧘‍♀", true); + emojiTrie.put("🧘🏻‍♀️", true); + emojiTrie.put("🧘🏻‍♀", true); + emojiTrie.put("🧘🏼‍♀️", true); + emojiTrie.put("🧘🏼‍♀", true); + emojiTrie.put("🧘🏽‍♀️", true); + emojiTrie.put("🧘🏽‍♀", true); + emojiTrie.put("🧘🏾‍♀️", true); + emojiTrie.put("🧘🏾‍♀", true); + emojiTrie.put("🧘🏿‍♀️", true); + emojiTrie.put("🧘🏿‍♀", true); + emojiTrie.put("🛀", true); + emojiTrie.put("🛀🏻", true); + emojiTrie.put("🛀🏼", true); + emojiTrie.put("🛀🏽", true); + emojiTrie.put("🛀🏾", true); + emojiTrie.put("🛀🏿", true); + emojiTrie.put("🛌", true); + emojiTrie.put("🛌🏻", true); + emojiTrie.put("🛌🏼", true); + emojiTrie.put("🛌🏽", true); + emojiTrie.put("🛌🏾", true); + emojiTrie.put("🛌🏿", true); + emojiTrie.put("🧑‍🤝‍🧑", true); + emojiTrie.put("🧑🏻‍🤝‍🧑🏻", true); + emojiTrie.put("🧑🏻‍🤝‍🧑🏼", true); + emojiTrie.put("🧑🏻‍🤝‍🧑🏽", true); + emojiTrie.put("🧑🏻‍🤝‍🧑🏾", true); + emojiTrie.put("🧑🏻‍🤝‍🧑🏿", true); + emojiTrie.put("🧑🏼‍🤝‍🧑🏻", true); + emojiTrie.put("🧑🏼‍🤝‍🧑🏼", true); + emojiTrie.put("🧑🏼‍🤝‍🧑🏽", true); + emojiTrie.put("🧑🏼‍🤝‍🧑🏾", true); + emojiTrie.put("🧑🏼‍🤝‍🧑🏿", true); + emojiTrie.put("🧑🏽‍🤝‍🧑🏻", true); + emojiTrie.put("🧑🏽‍🤝‍🧑🏼", true); + emojiTrie.put("🧑🏽‍🤝‍🧑🏽", true); + emojiTrie.put("🧑🏽‍🤝‍🧑🏾", true); + emojiTrie.put("🧑🏽‍🤝‍🧑🏿", true); + emojiTrie.put("🧑🏾‍🤝‍🧑🏻", true); + emojiTrie.put("🧑🏾‍🤝‍🧑🏼", true); + emojiTrie.put("🧑🏾‍🤝‍🧑🏽", true); + emojiTrie.put("🧑🏾‍🤝‍🧑🏾", true); + emojiTrie.put("🧑🏾‍🤝‍🧑🏿", true); + emojiTrie.put("🧑🏿‍🤝‍🧑🏻", true); + emojiTrie.put("🧑🏿‍🤝‍🧑🏼", true); + emojiTrie.put("🧑🏿‍🤝‍🧑🏽", true); + emojiTrie.put("🧑🏿‍🤝‍🧑🏾", true); + emojiTrie.put("🧑🏿‍🤝‍🧑🏿", true); + emojiTrie.put("👭", true); + emojiTrie.put("👭🏻", true); + emojiTrie.put("👩🏻‍🤝‍👩🏼", true); + emojiTrie.put("👩🏻‍🤝‍👩🏽", true); + emojiTrie.put("👩🏻‍🤝‍👩🏾", true); + emojiTrie.put("👩🏻‍🤝‍👩🏿", true); + emojiTrie.put("👩🏼‍🤝‍👩🏻", true); + emojiTrie.put("👭🏼", true); + emojiTrie.put("👩🏼‍🤝‍👩🏽", true); + emojiTrie.put("👩🏼‍🤝‍👩🏾", true); + emojiTrie.put("👩🏼‍🤝‍👩🏿", true); + emojiTrie.put("👩🏽‍🤝‍👩🏻", true); + emojiTrie.put("👩🏽‍🤝‍👩🏼", true); + emojiTrie.put("👭🏽", true); + emojiTrie.put("👩🏽‍🤝‍👩🏾", true); + emojiTrie.put("👩🏽‍🤝‍👩🏿", true); + emojiTrie.put("👩🏾‍🤝‍👩🏻", true); + emojiTrie.put("👩🏾‍🤝‍👩🏼", true); + emojiTrie.put("👩🏾‍🤝‍👩🏽", true); + emojiTrie.put("👭🏾", true); + emojiTrie.put("👩🏾‍🤝‍👩🏿", true); + emojiTrie.put("👩🏿‍🤝‍👩🏻", true); + emojiTrie.put("👩🏿‍🤝‍👩🏼", true); + emojiTrie.put("👩🏿‍🤝‍👩🏽", true); + emojiTrie.put("👩🏿‍🤝‍👩🏾", true); + emojiTrie.put("👭🏿", true); + emojiTrie.put("👫", true); + emojiTrie.put("👫🏻", true); + emojiTrie.put("👩🏻‍🤝‍👨🏼", true); + emojiTrie.put("👩🏻‍🤝‍👨🏽", true); + emojiTrie.put("👩🏻‍🤝‍👨🏾", true); + emojiTrie.put("👩🏻‍🤝‍👨🏿", true); + emojiTrie.put("👩🏼‍🤝‍👨🏻", true); + emojiTrie.put("👫🏼", true); + emojiTrie.put("👩🏼‍🤝‍👨🏽", true); + emojiTrie.put("👩🏼‍🤝‍👨🏾", true); + emojiTrie.put("👩🏼‍🤝‍👨🏿", true); + emojiTrie.put("👩🏽‍🤝‍👨🏻", true); + emojiTrie.put("👩🏽‍🤝‍👨🏼", true); + emojiTrie.put("👫🏽", true); + emojiTrie.put("👩🏽‍🤝‍👨🏾", true); + emojiTrie.put("👩🏽‍🤝‍👨🏿", true); + emojiTrie.put("👩🏾‍🤝‍👨🏻", true); + emojiTrie.put("👩🏾‍🤝‍👨🏼", true); + emojiTrie.put("👩🏾‍🤝‍👨🏽", true); + emojiTrie.put("👫🏾", true); + emojiTrie.put("👩🏾‍🤝‍👨🏿", true); + emojiTrie.put("👩🏿‍🤝‍👨🏻", true); + emojiTrie.put("👩🏿‍🤝‍👨🏼", true); + emojiTrie.put("👩🏿‍🤝‍👨🏽", true); + emojiTrie.put("👩🏿‍🤝‍👨🏾", true); + emojiTrie.put("👫🏿", true); + emojiTrie.put("👬", true); + emojiTrie.put("👬🏻", true); + emojiTrie.put("👨🏻‍🤝‍👨🏼", true); + emojiTrie.put("👨🏻‍🤝‍👨🏽", true); + emojiTrie.put("👨🏻‍🤝‍👨🏾", true); + emojiTrie.put("👨🏻‍🤝‍👨🏿", true); + emojiTrie.put("👨🏼‍🤝‍👨🏻", true); + emojiTrie.put("👬🏼", true); + emojiTrie.put("👨🏼‍🤝‍👨🏽", true); + emojiTrie.put("👨🏼‍🤝‍👨🏾", true); + emojiTrie.put("👨🏼‍🤝‍👨🏿", true); + emojiTrie.put("👨🏽‍🤝‍👨🏻", true); + emojiTrie.put("👨🏽‍🤝‍👨🏼", true); + emojiTrie.put("👬🏽", true); + emojiTrie.put("👨🏽‍🤝‍👨🏾", true); + emojiTrie.put("👨🏽‍🤝‍👨🏿", true); + emojiTrie.put("👨🏾‍🤝‍👨🏻", true); + emojiTrie.put("👨🏾‍🤝‍👨🏼", true); + emojiTrie.put("👨🏾‍🤝‍👨🏽", true); + emojiTrie.put("👬🏾", true); + emojiTrie.put("👨🏾‍🤝‍👨🏿", true); + emojiTrie.put("👨🏿‍🤝‍👨🏻", true); + emojiTrie.put("👨🏿‍🤝‍👨🏼", true); + emojiTrie.put("👨🏿‍🤝‍👨🏽", true); + emojiTrie.put("👨🏿‍🤝‍👨🏾", true); + emojiTrie.put("👬🏿", true); + emojiTrie.put("💏", true); + emojiTrie.put("💏🏻", true); + emojiTrie.put("💏🏼", true); + emojiTrie.put("💏🏽", true); + emojiTrie.put("💏🏾", true); + emojiTrie.put("💏🏿", true); + emojiTrie.put("🧑🏻‍❤️‍💋‍🧑🏼", true); + emojiTrie.put("🧑🏻‍❤‍💋‍🧑🏼", true); + emojiTrie.put("🧑🏻‍❤️‍💋‍🧑🏽", true); + emojiTrie.put("🧑🏻‍❤‍💋‍🧑🏽", true); + emojiTrie.put("🧑🏻‍❤️‍💋‍🧑🏾", true); + emojiTrie.put("🧑🏻‍❤‍💋‍🧑🏾", true); + emojiTrie.put("🧑🏻‍❤️‍💋‍🧑🏿", true); + emojiTrie.put("🧑🏻‍❤‍💋‍🧑🏿", true); + emojiTrie.put("🧑🏼‍❤️‍💋‍🧑🏻", true); + emojiTrie.put("🧑🏼‍❤‍💋‍🧑🏻", true); + emojiTrie.put("🧑🏼‍❤️‍💋‍🧑🏽", true); + emojiTrie.put("🧑🏼‍❤‍💋‍🧑🏽", true); + emojiTrie.put("🧑🏼‍❤️‍💋‍🧑🏾", true); + emojiTrie.put("🧑🏼‍❤‍💋‍🧑🏾", true); + emojiTrie.put("🧑🏼‍❤️‍💋‍🧑🏿", true); + emojiTrie.put("🧑🏼‍❤‍💋‍🧑🏿", true); + emojiTrie.put("🧑🏽‍❤️‍💋‍🧑🏻", true); + emojiTrie.put("🧑🏽‍❤‍💋‍🧑🏻", true); + emojiTrie.put("🧑🏽‍❤️‍💋‍🧑🏼", true); + emojiTrie.put("🧑🏽‍❤‍💋‍🧑🏼", true); + emojiTrie.put("🧑🏽‍❤️‍💋‍🧑🏾", true); + emojiTrie.put("🧑🏽‍❤‍💋‍🧑🏾", true); + emojiTrie.put("🧑🏽‍❤️‍💋‍🧑🏿", true); + emojiTrie.put("🧑🏽‍❤‍💋‍🧑🏿", true); + emojiTrie.put("🧑🏾‍❤️‍💋‍🧑🏻", true); + emojiTrie.put("🧑🏾‍❤‍💋‍🧑🏻", true); + emojiTrie.put("🧑🏾‍❤️‍💋‍🧑🏼", true); + emojiTrie.put("🧑🏾‍❤‍💋‍🧑🏼", true); + emojiTrie.put("🧑🏾‍❤️‍💋‍🧑🏽", true); + emojiTrie.put("🧑🏾‍❤‍💋‍🧑🏽", true); + emojiTrie.put("🧑🏾‍❤️‍💋‍🧑🏿", true); + emojiTrie.put("🧑🏾‍❤‍💋‍🧑🏿", true); + emojiTrie.put("🧑🏿‍❤️‍💋‍🧑🏻", true); + emojiTrie.put("🧑🏿‍❤‍💋‍🧑🏻", true); + emojiTrie.put("🧑🏿‍❤️‍💋‍🧑🏼", true); + emojiTrie.put("🧑🏿‍❤‍💋‍🧑🏼", true); + emojiTrie.put("🧑🏿‍❤️‍💋‍🧑🏽", true); + emojiTrie.put("🧑🏿‍❤‍💋‍🧑🏽", true); + emojiTrie.put("🧑🏿‍❤️‍💋‍🧑🏾", true); + emojiTrie.put("🧑🏿‍❤‍💋‍🧑🏾", true); + emojiTrie.put("👩‍❤️‍💋‍👨", true); + emojiTrie.put("👩‍❤‍💋‍👨", true); + emojiTrie.put("👩🏻‍❤️‍💋‍👨🏻", true); + emojiTrie.put("👩🏻‍❤‍💋‍👨🏻", true); + emojiTrie.put("👩🏻‍❤️‍💋‍👨🏼", true); + emojiTrie.put("👩🏻‍❤‍💋‍👨🏼", true); + emojiTrie.put("👩🏻‍❤️‍💋‍👨🏽", true); + emojiTrie.put("👩🏻‍❤‍💋‍👨🏽", true); + emojiTrie.put("👩🏻‍❤️‍💋‍👨🏾", true); + emojiTrie.put("👩🏻‍❤‍💋‍👨🏾", true); + emojiTrie.put("👩🏻‍❤️‍💋‍👨🏿", true); + emojiTrie.put("👩🏻‍❤‍💋‍👨🏿", true); + emojiTrie.put("👩🏼‍❤️‍💋‍👨🏻", true); + emojiTrie.put("👩🏼‍❤‍💋‍👨🏻", true); + emojiTrie.put("👩🏼‍❤️‍💋‍👨🏼", true); + emojiTrie.put("👩🏼‍❤‍💋‍👨🏼", true); + emojiTrie.put("👩🏼‍❤️‍💋‍👨🏽", true); + emojiTrie.put("👩🏼‍❤‍💋‍👨🏽", true); + emojiTrie.put("👩🏼‍❤️‍💋‍👨🏾", true); + emojiTrie.put("👩🏼‍❤‍💋‍👨🏾", true); + emojiTrie.put("👩🏼‍❤️‍💋‍👨🏿", true); + emojiTrie.put("👩🏼‍❤‍💋‍👨🏿", true); + emojiTrie.put("👩🏽‍❤️‍💋‍👨🏻", true); + emojiTrie.put("👩🏽‍❤‍💋‍👨🏻", true); + emojiTrie.put("👩🏽‍❤️‍💋‍👨🏼", true); + emojiTrie.put("👩🏽‍❤‍💋‍👨🏼", true); + emojiTrie.put("👩🏽‍❤️‍💋‍👨🏽", true); + emojiTrie.put("👩🏽‍❤‍💋‍👨🏽", true); + emojiTrie.put("👩🏽‍❤️‍💋‍👨🏾", true); + emojiTrie.put("👩🏽‍❤‍💋‍👨🏾", true); + emojiTrie.put("👩🏽‍❤️‍💋‍👨🏿", true); + emojiTrie.put("👩🏽‍❤‍💋‍👨🏿", true); + emojiTrie.put("👩🏾‍❤️‍💋‍👨🏻", true); + emojiTrie.put("👩🏾‍❤‍💋‍👨🏻", true); + emojiTrie.put("👩🏾‍❤️‍💋‍👨🏼", true); + emojiTrie.put("👩🏾‍❤‍💋‍👨🏼", true); + emojiTrie.put("👩🏾‍❤️‍💋‍👨🏽", true); + emojiTrie.put("👩🏾‍❤‍💋‍👨🏽", true); + emojiTrie.put("👩🏾‍❤️‍💋‍👨🏾", true); + emojiTrie.put("👩🏾‍❤‍💋‍👨🏾", true); + emojiTrie.put("👩🏾‍❤️‍💋‍👨🏿", true); + emojiTrie.put("👩🏾‍❤‍💋‍👨🏿", true); + emojiTrie.put("👩🏿‍❤️‍💋‍👨🏻", true); + emojiTrie.put("👩🏿‍❤‍💋‍👨🏻", true); + emojiTrie.put("👩🏿‍❤️‍💋‍👨🏼", true); + emojiTrie.put("👩🏿‍❤‍💋‍👨🏼", true); + emojiTrie.put("👩🏿‍❤️‍💋‍👨🏽", true); + emojiTrie.put("👩🏿‍❤‍💋‍👨🏽", true); + emojiTrie.put("👩🏿‍❤️‍💋‍👨🏾", true); + emojiTrie.put("👩🏿‍❤‍💋‍👨🏾", true); + emojiTrie.put("👩🏿‍❤️‍💋‍👨🏿", true); + emojiTrie.put("👩🏿‍❤‍💋‍👨🏿", true); + emojiTrie.put("👨‍❤️‍💋‍👨", true); + emojiTrie.put("👨‍❤‍💋‍👨", true); + emojiTrie.put("👨🏻‍❤️‍💋‍👨🏻", true); + emojiTrie.put("👨🏻‍❤‍💋‍👨🏻", true); + emojiTrie.put("👨🏻‍❤️‍💋‍👨🏼", true); + emojiTrie.put("👨🏻‍❤‍💋‍👨🏼", true); + emojiTrie.put("👨🏻‍❤️‍💋‍👨🏽", true); + emojiTrie.put("👨🏻‍❤‍💋‍👨🏽", true); + emojiTrie.put("👨🏻‍❤️‍💋‍👨🏾", true); + emojiTrie.put("👨🏻‍❤‍💋‍👨🏾", true); + emojiTrie.put("👨🏻‍❤️‍💋‍👨🏿", true); + emojiTrie.put("👨🏻‍❤‍💋‍👨🏿", true); + emojiTrie.put("👨🏼‍❤️‍💋‍👨🏻", true); + emojiTrie.put("👨🏼‍❤‍💋‍👨🏻", true); + emojiTrie.put("👨🏼‍❤️‍💋‍👨🏼", true); + emojiTrie.put("👨🏼‍❤‍💋‍👨🏼", true); + emojiTrie.put("👨🏼‍❤️‍💋‍👨🏽", true); + emojiTrie.put("👨🏼‍❤‍💋‍👨🏽", true); + emojiTrie.put("👨🏼‍❤️‍💋‍👨🏾", true); + emojiTrie.put("👨🏼‍❤‍💋‍👨🏾", true); + emojiTrie.put("👨🏼‍❤️‍💋‍👨🏿", true); + emojiTrie.put("👨🏼‍❤‍💋‍👨🏿", true); + emojiTrie.put("👨🏽‍❤️‍💋‍👨🏻", true); + emojiTrie.put("👨🏽‍❤‍💋‍👨🏻", true); + emojiTrie.put("👨🏽‍❤️‍💋‍👨🏼", true); + emojiTrie.put("👨🏽‍❤‍💋‍👨🏼", true); + emojiTrie.put("👨🏽‍❤️‍💋‍👨🏽", true); + emojiTrie.put("👨🏽‍❤‍💋‍👨🏽", true); + emojiTrie.put("👨🏽‍❤️‍💋‍👨🏾", true); + emojiTrie.put("👨🏽‍❤‍💋‍👨🏾", true); + emojiTrie.put("👨🏽‍❤️‍💋‍👨🏿", true); + emojiTrie.put("👨🏽‍❤‍💋‍👨🏿", true); + emojiTrie.put("👨🏾‍❤️‍💋‍👨🏻", true); + emojiTrie.put("👨🏾‍❤‍💋‍👨🏻", true); + emojiTrie.put("👨🏾‍❤️‍💋‍👨🏼", true); + emojiTrie.put("👨🏾‍❤‍💋‍👨🏼", true); + emojiTrie.put("👨🏾‍❤️‍💋‍👨🏽", true); + emojiTrie.put("👨🏾‍❤‍💋‍👨🏽", true); + emojiTrie.put("👨🏾‍❤️‍💋‍👨🏾", true); + emojiTrie.put("👨🏾‍❤‍💋‍👨🏾", true); + emojiTrie.put("👨🏾‍❤️‍💋‍👨🏿", true); + emojiTrie.put("👨🏾‍❤‍💋‍👨🏿", true); + emojiTrie.put("👨🏿‍❤️‍💋‍👨🏻", true); + emojiTrie.put("👨🏿‍❤‍💋‍👨🏻", true); + emojiTrie.put("👨🏿‍❤️‍💋‍👨🏼", true); + emojiTrie.put("👨🏿‍❤‍💋‍👨🏼", true); + emojiTrie.put("👨🏿‍❤️‍💋‍👨🏽", true); + emojiTrie.put("👨🏿‍❤‍💋‍👨🏽", true); + emojiTrie.put("👨🏿‍❤️‍💋‍👨🏾", true); + emojiTrie.put("👨🏿‍❤‍💋‍👨🏾", true); + emojiTrie.put("👨🏿‍❤️‍💋‍👨🏿", true); + emojiTrie.put("👨🏿‍❤‍💋‍👨🏿", true); + emojiTrie.put("👩‍❤️‍💋‍👩", true); + emojiTrie.put("👩‍❤‍💋‍👩", true); + emojiTrie.put("👩🏻‍❤️‍💋‍👩🏻", true); + emojiTrie.put("👩🏻‍❤‍💋‍👩🏻", true); + emojiTrie.put("👩🏻‍❤️‍💋‍👩🏼", true); + emojiTrie.put("👩🏻‍❤‍💋‍👩🏼", true); + emojiTrie.put("👩🏻‍❤️‍💋‍👩🏽", true); + emojiTrie.put("👩🏻‍❤‍💋‍👩🏽", true); + emojiTrie.put("👩🏻‍❤️‍💋‍👩🏾", true); + emojiTrie.put("👩🏻‍❤‍💋‍👩🏾", true); + emojiTrie.put("👩🏻‍❤️‍💋‍👩🏿", true); + emojiTrie.put("👩🏻‍❤‍💋‍👩🏿", true); + emojiTrie.put("👩🏼‍❤️‍💋‍👩🏻", true); + emojiTrie.put("👩🏼‍❤‍💋‍👩🏻", true); + emojiTrie.put("👩🏼‍❤️‍💋‍👩🏼", true); + emojiTrie.put("👩🏼‍❤‍💋‍👩🏼", true); + emojiTrie.put("👩🏼‍❤️‍💋‍👩🏽", true); + emojiTrie.put("👩🏼‍❤‍💋‍👩🏽", true); + emojiTrie.put("👩🏼‍❤️‍💋‍👩🏾", true); + emojiTrie.put("👩🏼‍❤‍💋‍👩🏾", true); + emojiTrie.put("👩🏼‍❤️‍💋‍👩🏿", true); + emojiTrie.put("👩🏼‍❤‍💋‍👩🏿", true); + emojiTrie.put("👩🏽‍❤️‍💋‍👩🏻", true); + emojiTrie.put("👩🏽‍❤‍💋‍👩🏻", true); + emojiTrie.put("👩🏽‍❤️‍💋‍👩🏼", true); + emojiTrie.put("👩🏽‍❤‍💋‍👩🏼", true); + emojiTrie.put("👩🏽‍❤️‍💋‍👩🏽", true); + emojiTrie.put("👩🏽‍❤‍💋‍👩🏽", true); + emojiTrie.put("👩🏽‍❤️‍💋‍👩🏾", true); + emojiTrie.put("👩🏽‍❤‍💋‍👩🏾", true); + emojiTrie.put("👩🏽‍❤️‍💋‍👩🏿", true); + emojiTrie.put("👩🏽‍❤‍💋‍👩🏿", true); + emojiTrie.put("👩🏾‍❤️‍💋‍👩🏻", true); + emojiTrie.put("👩🏾‍❤‍💋‍👩🏻", true); + emojiTrie.put("👩🏾‍❤️‍💋‍👩🏼", true); + emojiTrie.put("👩🏾‍❤‍💋‍👩🏼", true); + emojiTrie.put("👩🏾‍❤️‍💋‍👩🏽", true); + emojiTrie.put("👩🏾‍❤‍💋‍👩🏽", true); + emojiTrie.put("👩🏾‍❤️‍💋‍👩🏾", true); + emojiTrie.put("👩🏾‍❤‍💋‍👩🏾", true); + emojiTrie.put("👩🏾‍❤️‍💋‍👩🏿", true); + emojiTrie.put("👩🏾‍❤‍💋‍👩🏿", true); + emojiTrie.put("👩🏿‍❤️‍💋‍👩🏻", true); + emojiTrie.put("👩🏿‍❤‍💋‍👩🏻", true); + emojiTrie.put("👩🏿‍❤️‍💋‍👩🏼", true); + emojiTrie.put("👩🏿‍❤‍💋‍👩🏼", true); + emojiTrie.put("👩🏿‍❤️‍💋‍👩🏽", true); + emojiTrie.put("👩🏿‍❤‍💋‍👩🏽", true); + emojiTrie.put("👩🏿‍❤️‍💋‍👩🏾", true); + emojiTrie.put("👩🏿‍❤‍💋‍👩🏾", true); + emojiTrie.put("👩🏿‍❤️‍💋‍👩🏿", true); + emojiTrie.put("👩🏿‍❤‍💋‍👩🏿", true); + emojiTrie.put("💑", true); + emojiTrie.put("💑🏻", true); + emojiTrie.put("💑🏼", true); + emojiTrie.put("💑🏽", true); + emojiTrie.put("💑🏾", true); + emojiTrie.put("💑🏿", true); + emojiTrie.put("🧑🏻‍❤️‍🧑🏼", true); + emojiTrie.put("🧑🏻‍❤‍🧑🏼", true); + emojiTrie.put("🧑🏻‍❤️‍🧑🏽", true); + emojiTrie.put("🧑🏻‍❤‍🧑🏽", true); + emojiTrie.put("🧑🏻‍❤️‍🧑🏾", true); + emojiTrie.put("🧑🏻‍❤‍🧑🏾", true); + emojiTrie.put("🧑🏻‍❤️‍🧑🏿", true); + emojiTrie.put("🧑🏻‍❤‍🧑🏿", true); + emojiTrie.put("🧑🏼‍❤️‍🧑🏻", true); + emojiTrie.put("🧑🏼‍❤‍🧑🏻", true); + emojiTrie.put("🧑🏼‍❤️‍🧑🏽", true); + emojiTrie.put("🧑🏼‍❤‍🧑🏽", true); + emojiTrie.put("🧑🏼‍❤️‍🧑🏾", true); + emojiTrie.put("🧑🏼‍❤‍🧑🏾", true); + emojiTrie.put("🧑🏼‍❤️‍🧑🏿", true); + emojiTrie.put("🧑🏼‍❤‍🧑🏿", true); + emojiTrie.put("🧑🏽‍❤️‍🧑🏻", true); + emojiTrie.put("🧑🏽‍❤‍🧑🏻", true); + emojiTrie.put("🧑🏽‍❤️‍🧑🏼", true); + emojiTrie.put("🧑🏽‍❤‍🧑🏼", true); + emojiTrie.put("🧑🏽‍❤️‍🧑🏾", true); + emojiTrie.put("🧑🏽‍❤‍🧑🏾", true); + emojiTrie.put("🧑🏽‍❤️‍🧑🏿", true); + emojiTrie.put("🧑🏽‍❤‍🧑🏿", true); + emojiTrie.put("🧑🏾‍❤️‍🧑🏻", true); + emojiTrie.put("🧑🏾‍❤‍🧑🏻", true); + emojiTrie.put("🧑🏾‍❤️‍🧑🏼", true); + emojiTrie.put("🧑🏾‍❤‍🧑🏼", true); + emojiTrie.put("🧑🏾‍❤️‍🧑🏽", true); + emojiTrie.put("🧑🏾‍❤‍🧑🏽", true); + emojiTrie.put("🧑🏾‍❤️‍🧑🏿", true); + emojiTrie.put("🧑🏾‍❤‍🧑🏿", true); + emojiTrie.put("🧑🏿‍❤️‍🧑🏻", true); + emojiTrie.put("🧑🏿‍❤‍🧑🏻", true); + emojiTrie.put("🧑🏿‍❤️‍🧑🏼", true); + emojiTrie.put("🧑🏿‍❤‍🧑🏼", true); + emojiTrie.put("🧑🏿‍❤️‍🧑🏽", true); + emojiTrie.put("🧑🏿‍❤‍🧑🏽", true); + emojiTrie.put("🧑🏿‍❤️‍🧑🏾", true); + emojiTrie.put("🧑🏿‍❤‍🧑🏾", true); + emojiTrie.put("👩‍❤️‍👨", true); + emojiTrie.put("👩‍❤‍👨", true); + emojiTrie.put("👩🏻‍❤️‍👨🏻", true); + emojiTrie.put("👩🏻‍❤‍👨🏻", true); + emojiTrie.put("👩🏻‍❤️‍👨🏼", true); + emojiTrie.put("👩🏻‍❤‍👨🏼", true); + emojiTrie.put("👩🏻‍❤️‍👨🏽", true); + emojiTrie.put("👩🏻‍❤‍👨🏽", true); + emojiTrie.put("👩🏻‍❤️‍👨🏾", true); + emojiTrie.put("👩🏻‍❤‍👨🏾", true); + emojiTrie.put("👩🏻‍❤️‍👨🏿", true); + emojiTrie.put("👩🏻‍❤‍👨🏿", true); + emojiTrie.put("👩🏼‍❤️‍👨🏻", true); + emojiTrie.put("👩🏼‍❤‍👨🏻", true); + emojiTrie.put("👩🏼‍❤️‍👨🏼", true); + emojiTrie.put("👩🏼‍❤‍👨🏼", true); + emojiTrie.put("👩🏼‍❤️‍👨🏽", true); + emojiTrie.put("👩🏼‍❤‍👨🏽", true); + emojiTrie.put("👩🏼‍❤️‍👨🏾", true); + emojiTrie.put("👩🏼‍❤‍👨🏾", true); + emojiTrie.put("👩🏼‍❤️‍👨🏿", true); + emojiTrie.put("👩🏼‍❤‍👨🏿", true); + emojiTrie.put("👩🏽‍❤️‍👨🏻", true); + emojiTrie.put("👩🏽‍❤‍👨🏻", true); + emojiTrie.put("👩🏽‍❤️‍👨🏼", true); + emojiTrie.put("👩🏽‍❤‍👨🏼", true); + emojiTrie.put("👩🏽‍❤️‍👨🏽", true); + emojiTrie.put("👩🏽‍❤‍👨🏽", true); + emojiTrie.put("👩🏽‍❤️‍👨🏾", true); + emojiTrie.put("👩🏽‍❤‍👨🏾", true); + emojiTrie.put("👩🏽‍❤️‍👨🏿", true); + emojiTrie.put("👩🏽‍❤‍👨🏿", true); + emojiTrie.put("👩🏾‍❤️‍👨🏻", true); + emojiTrie.put("👩🏾‍❤‍👨🏻", true); + emojiTrie.put("👩🏾‍❤️‍👨🏼", true); + emojiTrie.put("👩🏾‍❤‍👨🏼", true); + emojiTrie.put("👩🏾‍❤️‍👨🏽", true); + emojiTrie.put("👩🏾‍❤‍👨🏽", true); + emojiTrie.put("👩🏾‍❤️‍👨🏾", true); + emojiTrie.put("👩🏾‍❤‍👨🏾", true); + emojiTrie.put("👩🏾‍❤️‍👨🏿", true); + emojiTrie.put("👩🏾‍❤‍👨🏿", true); + emojiTrie.put("👩🏿‍❤️‍👨🏻", true); + emojiTrie.put("👩🏿‍❤‍👨🏻", true); + emojiTrie.put("👩🏿‍❤️‍👨🏼", true); + emojiTrie.put("👩🏿‍❤‍👨🏼", true); + emojiTrie.put("👩🏿‍❤️‍👨🏽", true); + emojiTrie.put("👩🏿‍❤‍👨🏽", true); + emojiTrie.put("👩🏿‍❤️‍👨🏾", true); + emojiTrie.put("👩🏿‍❤‍👨🏾", true); + emojiTrie.put("👩🏿‍❤️‍👨🏿", true); + emojiTrie.put("👩🏿‍❤‍👨🏿", true); + emojiTrie.put("👨‍❤️‍👨", true); + emojiTrie.put("👨‍❤‍👨", true); + emojiTrie.put("👨🏻‍❤️‍👨🏻", true); + emojiTrie.put("👨🏻‍❤‍👨🏻", true); + emojiTrie.put("👨🏻‍❤️‍👨🏼", true); + emojiTrie.put("👨🏻‍❤‍👨🏼", true); + emojiTrie.put("👨🏻‍❤️‍👨🏽", true); + emojiTrie.put("👨🏻‍❤‍👨🏽", true); + emojiTrie.put("👨🏻‍❤️‍👨🏾", true); + emojiTrie.put("👨🏻‍❤‍👨🏾", true); + emojiTrie.put("👨🏻‍❤️‍👨🏿", true); + emojiTrie.put("👨🏻‍❤‍👨🏿", true); + emojiTrie.put("👨🏼‍❤️‍👨🏻", true); + emojiTrie.put("👨🏼‍❤‍👨🏻", true); + emojiTrie.put("👨🏼‍❤️‍👨🏼", true); + emojiTrie.put("👨🏼‍❤‍👨🏼", true); + emojiTrie.put("👨🏼‍❤️‍👨🏽", true); + emojiTrie.put("👨🏼‍❤‍👨🏽", true); + emojiTrie.put("👨🏼‍❤️‍👨🏾", true); + emojiTrie.put("👨🏼‍❤‍👨🏾", true); + emojiTrie.put("👨🏼‍❤️‍👨🏿", true); + emojiTrie.put("👨🏼‍❤‍👨🏿", true); + emojiTrie.put("👨🏽‍❤️‍👨🏻", true); + emojiTrie.put("👨🏽‍❤‍👨🏻", true); + emojiTrie.put("👨🏽‍❤️‍👨🏼", true); + emojiTrie.put("👨🏽‍❤‍👨🏼", true); + emojiTrie.put("👨🏽‍❤️‍👨🏽", true); + emojiTrie.put("👨🏽‍❤‍👨🏽", true); + emojiTrie.put("👨🏽‍❤️‍👨🏾", true); + emojiTrie.put("👨🏽‍❤‍👨🏾", true); + emojiTrie.put("👨🏽‍❤️‍👨🏿", true); + emojiTrie.put("👨🏽‍❤‍👨🏿", true); + emojiTrie.put("👨🏾‍❤️‍👨🏻", true); + emojiTrie.put("👨🏾‍❤‍👨🏻", true); + emojiTrie.put("👨🏾‍❤️‍👨🏼", true); + emojiTrie.put("👨🏾‍❤‍👨🏼", true); + emojiTrie.put("👨🏾‍❤️‍👨🏽", true); + emojiTrie.put("👨🏾‍❤‍👨🏽", true); + emojiTrie.put("👨🏾‍❤️‍👨🏾", true); + emojiTrie.put("👨🏾‍❤‍👨🏾", true); + emojiTrie.put("👨🏾‍❤️‍👨🏿", true); + emojiTrie.put("👨🏾‍❤‍👨🏿", true); + emojiTrie.put("👨🏿‍❤️‍👨🏻", true); + emojiTrie.put("👨🏿‍❤‍👨🏻", true); + emojiTrie.put("👨🏿‍❤️‍👨🏼", true); + emojiTrie.put("👨🏿‍❤‍👨🏼", true); + emojiTrie.put("👨🏿‍❤️‍👨🏽", true); + emojiTrie.put("👨🏿‍❤‍👨🏽", true); + emojiTrie.put("👨🏿‍❤️‍👨🏾", true); + emojiTrie.put("👨🏿‍❤‍👨🏾", true); + emojiTrie.put("👨🏿‍❤️‍👨🏿", true); + emojiTrie.put("👨🏿‍❤‍👨🏿", true); + emojiTrie.put("👩‍❤️‍👩", true); + emojiTrie.put("👩‍❤‍👩", true); + emojiTrie.put("👩🏻‍❤️‍👩🏻", true); + emojiTrie.put("👩🏻‍❤‍👩🏻", true); + emojiTrie.put("👩🏻‍❤️‍👩🏼", true); + emojiTrie.put("👩🏻‍❤‍👩🏼", true); + emojiTrie.put("👩🏻‍❤️‍👩🏽", true); + emojiTrie.put("👩🏻‍❤‍👩🏽", true); + emojiTrie.put("👩🏻‍❤️‍👩🏾", true); + emojiTrie.put("👩🏻‍❤‍👩🏾", true); + emojiTrie.put("👩🏻‍❤️‍👩🏿", true); + emojiTrie.put("👩🏻‍❤‍👩🏿", true); + emojiTrie.put("👩🏼‍❤️‍👩🏻", true); + emojiTrie.put("👩🏼‍❤‍👩🏻", true); + emojiTrie.put("👩🏼‍❤️‍👩🏼", true); + emojiTrie.put("👩🏼‍❤‍👩🏼", true); + emojiTrie.put("👩🏼‍❤️‍👩🏽", true); + emojiTrie.put("👩🏼‍❤‍👩🏽", true); + emojiTrie.put("👩🏼‍❤️‍👩🏾", true); + emojiTrie.put("👩🏼‍❤‍👩🏾", true); + emojiTrie.put("👩🏼‍❤️‍👩🏿", true); + emojiTrie.put("👩🏼‍❤‍👩🏿", true); + emojiTrie.put("👩🏽‍❤️‍👩🏻", true); + emojiTrie.put("👩🏽‍❤‍👩🏻", true); + emojiTrie.put("👩🏽‍❤️‍👩🏼", true); + emojiTrie.put("👩🏽‍❤‍👩🏼", true); + emojiTrie.put("👩🏽‍❤️‍👩🏽", true); + emojiTrie.put("👩🏽‍❤‍👩🏽", true); + emojiTrie.put("👩🏽‍❤️‍👩🏾", true); + emojiTrie.put("👩🏽‍❤‍👩🏾", true); + emojiTrie.put("👩🏽‍❤️‍👩🏿", true); + emojiTrie.put("👩🏽‍❤‍👩🏿", true); + emojiTrie.put("👩🏾‍❤️‍👩🏻", true); + emojiTrie.put("👩🏾‍❤‍👩🏻", true); + emojiTrie.put("👩🏾‍❤️‍👩🏼", true); + emojiTrie.put("👩🏾‍❤‍👩🏼", true); + emojiTrie.put("👩🏾‍❤️‍👩🏽", true); + emojiTrie.put("👩🏾‍❤‍👩🏽", true); + emojiTrie.put("👩🏾‍❤️‍👩🏾", true); + emojiTrie.put("👩🏾‍❤‍👩🏾", true); + emojiTrie.put("👩🏾‍❤️‍👩🏿", true); + emojiTrie.put("👩🏾‍❤‍👩🏿", true); + emojiTrie.put("👩🏿‍❤️‍👩🏻", true); + emojiTrie.put("👩🏿‍❤‍👩🏻", true); + emojiTrie.put("👩🏿‍❤️‍👩🏼", true); + emojiTrie.put("👩🏿‍❤‍👩🏼", true); + emojiTrie.put("👩🏿‍❤️‍👩🏽", true); + emojiTrie.put("👩🏿‍❤‍👩🏽", true); + emojiTrie.put("👩🏿‍❤️‍👩🏾", true); + emojiTrie.put("👩🏿‍❤‍👩🏾", true); + emojiTrie.put("👩🏿‍❤️‍👩🏿", true); + emojiTrie.put("👩🏿‍❤‍👩🏿", true); + emojiTrie.put("👪", true); + emojiTrie.put("👨‍👩‍👦", true); + emojiTrie.put("👨‍👩‍👧", true); + emojiTrie.put("👨‍👩‍👧‍👦", true); + emojiTrie.put("👨‍👩‍👦‍👦", true); + emojiTrie.put("👨‍👩‍👧‍👧", true); + emojiTrie.put("👨‍👨‍👦", true); + emojiTrie.put("👨‍👨‍👧", true); + emojiTrie.put("👨‍👨‍👧‍👦", true); + emojiTrie.put("👨‍👨‍👦‍👦", true); + emojiTrie.put("👨‍👨‍👧‍👧", true); + emojiTrie.put("👩‍👩‍👦", true); + emojiTrie.put("👩‍👩‍👧", true); + emojiTrie.put("👩‍👩‍👧‍👦", true); + emojiTrie.put("👩‍👩‍👦‍👦", true); + emojiTrie.put("👩‍👩‍👧‍👧", true); + emojiTrie.put("👨‍👦", true); + emojiTrie.put("👨‍👦‍👦", true); + emojiTrie.put("👨‍👧", true); + emojiTrie.put("👨‍👧‍👦", true); + emojiTrie.put("👨‍👧‍👧", true); + emojiTrie.put("👩‍👦", true); + emojiTrie.put("👩‍👦‍👦", true); + emojiTrie.put("👩‍👧", true); + emojiTrie.put("👩‍👧‍👦", true); + emojiTrie.put("👩‍👧‍👧", true); + emojiTrie.put("🗣️", true); + emojiTrie.put("🗣", true); + emojiTrie.put("👤", true); + emojiTrie.put("👥", true); + emojiTrie.put("🫂", true); + emojiTrie.put("👣", true); + emojiTrie.put("🏻", true); + emojiTrie.put("🏼", true); + emojiTrie.put("🏽", true); + emojiTrie.put("🏾", true); + emojiTrie.put("🏿", true); + emojiTrie.put("🦰", true); + emojiTrie.put("🦱", true); + emojiTrie.put("🦳", true); + emojiTrie.put("🦲", true); + emojiTrie.put("🐵", true); + emojiTrie.put("🐒", true); + emojiTrie.put("🦍", true); + emojiTrie.put("🦧", true); + emojiTrie.put("🐶", true); + emojiTrie.put("🐕", true); + emojiTrie.put("🦮", true); + emojiTrie.put("🐕‍🦺", true); + emojiTrie.put("🐩", true); + emojiTrie.put("🐺", true); + emojiTrie.put("🦊", true); + emojiTrie.put("🦝", true); + emojiTrie.put("🐱", true); + emojiTrie.put("🐈", true); + emojiTrie.put("🐈‍⬛", true); + emojiTrie.put("🦁", true); + emojiTrie.put("🐯", true); + emojiTrie.put("🐅", true); + emojiTrie.put("🐆", true); + emojiTrie.put("🐴", true); + emojiTrie.put("🫎", true); + emojiTrie.put("🫏", true); + emojiTrie.put("🐎", true); + emojiTrie.put("🦄", true); + emojiTrie.put("🦓", true); + emojiTrie.put("🦌", true); + emojiTrie.put("🦬", true); + emojiTrie.put("🐮", true); + emojiTrie.put("🐂", true); + emojiTrie.put("🐃", true); + emojiTrie.put("🐄", true); + emojiTrie.put("🐷", true); + emojiTrie.put("🐖", true); + emojiTrie.put("🐗", true); + emojiTrie.put("🐽", true); + emojiTrie.put("🐏", true); + emojiTrie.put("🐑", true); + emojiTrie.put("🐐", true); + emojiTrie.put("🐪", true); + emojiTrie.put("🐫", true); + emojiTrie.put("🦙", true); + emojiTrie.put("🦒", true); + emojiTrie.put("🐘", true); + emojiTrie.put("🦣", true); + emojiTrie.put("🦏", true); + emojiTrie.put("🦛", true); + emojiTrie.put("🐭", true); + emojiTrie.put("🐁", true); + emojiTrie.put("🐀", true); + emojiTrie.put("🐹", true); + emojiTrie.put("🐰", true); + emojiTrie.put("🐇", true); + emojiTrie.put("🐿️", true); + emojiTrie.put("🐿", true); + emojiTrie.put("🦫", true); + emojiTrie.put("🦔", true); + emojiTrie.put("🦇", true); + emojiTrie.put("🐻", true); + emojiTrie.put("🐻‍❄️", true); + emojiTrie.put("🐻‍❄", true); + emojiTrie.put("🐨", true); + emojiTrie.put("🐼", true); + emojiTrie.put("🦥", true); + emojiTrie.put("🦦", true); + emojiTrie.put("🦨", true); + emojiTrie.put("🦘", true); + emojiTrie.put("🦡", true); + emojiTrie.put("🐾", true); + emojiTrie.put("🦃", true); + emojiTrie.put("🐔", true); + emojiTrie.put("🐓", true); + emojiTrie.put("🐣", true); + emojiTrie.put("🐤", true); + emojiTrie.put("🐥", true); + emojiTrie.put("🐦", true); + emojiTrie.put("🐧", true); + emojiTrie.put("🕊️", true); + emojiTrie.put("🕊", true); + emojiTrie.put("🦅", true); + emojiTrie.put("🦆", true); + emojiTrie.put("🦢", true); + emojiTrie.put("🦉", true); + emojiTrie.put("🦤", true); + emojiTrie.put("🪶", true); + emojiTrie.put("🦩", true); + emojiTrie.put("🦚", true); + emojiTrie.put("🦜", true); + emojiTrie.put("🪽", true); + emojiTrie.put("🐦‍⬛", true); + emojiTrie.put("🪿", true); + emojiTrie.put("🐸", true); + emojiTrie.put("🐊", true); + emojiTrie.put("🐢", true); + emojiTrie.put("🦎", true); + emojiTrie.put("🐍", true); + emojiTrie.put("🐲", true); + emojiTrie.put("🐉", true); + emojiTrie.put("🦕", true); + emojiTrie.put("🦖", true); + emojiTrie.put("🐳", true); + emojiTrie.put("🐋", true); + emojiTrie.put("🐬", true); + emojiTrie.put("🦭", true); + emojiTrie.put("🐟", true); + emojiTrie.put("🐠", true); + emojiTrie.put("🐡", true); + emojiTrie.put("🦈", true); + emojiTrie.put("🐙", true); + emojiTrie.put("🐚", true); + emojiTrie.put("🪸", true); + emojiTrie.put("🪼", true); + emojiTrie.put("🐌", true); + emojiTrie.put("🦋", true); + emojiTrie.put("🐛", true); + emojiTrie.put("🐜", true); + emojiTrie.put("🐝", true); + emojiTrie.put("🪲", true); + emojiTrie.put("🐞", true); + emojiTrie.put("🦗", true); + emojiTrie.put("🪳", true); + emojiTrie.put("🕷️", true); + emojiTrie.put("🕷", true); + emojiTrie.put("🕸️", true); + emojiTrie.put("🕸", true); + emojiTrie.put("🦂", true); + emojiTrie.put("🦟", true); + emojiTrie.put("🪰", true); + emojiTrie.put("🪱", true); + emojiTrie.put("🦠", true); + emojiTrie.put("💐", true); + emojiTrie.put("🌸", true); + emojiTrie.put("💮", true); + emojiTrie.put("🪷", true); + emojiTrie.put("🏵️", true); + emojiTrie.put("🏵", true); + emojiTrie.put("🌹", true); + emojiTrie.put("🥀", true); + emojiTrie.put("🌺", true); + emojiTrie.put("🌻", true); + emojiTrie.put("🌼", true); + emojiTrie.put("🌷", true); + emojiTrie.put("🪻", true); + emojiTrie.put("🌱", true); + emojiTrie.put("🪴", true); + emojiTrie.put("🌲", true); + emojiTrie.put("🌳", true); + emojiTrie.put("🌴", true); + emojiTrie.put("🌵", true); + emojiTrie.put("🌾", true); + emojiTrie.put("🌿", true); + emojiTrie.put("☘️", true); + emojiTrie.put("☘", true); + emojiTrie.put("🍀", true); + emojiTrie.put("🍁", true); + emojiTrie.put("🍂", true); + emojiTrie.put("🍃", true); + emojiTrie.put("🪹", true); + emojiTrie.put("🪺", true); + emojiTrie.put("🍄", true); + emojiTrie.put("🍇", true); + emojiTrie.put("🍈", true); + emojiTrie.put("🍉", true); + emojiTrie.put("🍊", true); + emojiTrie.put("🍋", true); + emojiTrie.put("🍌", true); + emojiTrie.put("🍍", true); + emojiTrie.put("🥭", true); + emojiTrie.put("🍎", true); + emojiTrie.put("🍏", true); + emojiTrie.put("🍐", true); + emojiTrie.put("🍑", true); + emojiTrie.put("🍒", true); + emojiTrie.put("🍓", true); + emojiTrie.put("🫐", true); + emojiTrie.put("🥝", true); + emojiTrie.put("🍅", true); + emojiTrie.put("🫒", true); + emojiTrie.put("🥥", true); + emojiTrie.put("🥑", true); + emojiTrie.put("🍆", true); + emojiTrie.put("🥔", true); + emojiTrie.put("🥕", true); + emojiTrie.put("🌽", true); + emojiTrie.put("🌶️", true); + emojiTrie.put("🌶", true); + emojiTrie.put("🫑", true); + emojiTrie.put("🥒", true); + emojiTrie.put("🥬", true); + emojiTrie.put("🥦", true); + emojiTrie.put("🧄", true); + emojiTrie.put("🧅", true); + emojiTrie.put("🥜", true); + emojiTrie.put("🫘", true); + emojiTrie.put("🌰", true); + emojiTrie.put("🫚", true); + emojiTrie.put("🫛", true); + emojiTrie.put("🍞", true); + emojiTrie.put("🥐", true); + emojiTrie.put("🥖", true); + emojiTrie.put("🫓", true); + emojiTrie.put("🥨", true); + emojiTrie.put("🥯", true); + emojiTrie.put("🥞", true); + emojiTrie.put("🧇", true); + emojiTrie.put("🧀", true); + emojiTrie.put("🍖", true); + emojiTrie.put("🍗", true); + emojiTrie.put("🥩", true); + emojiTrie.put("🥓", true); + emojiTrie.put("🍔", true); + emojiTrie.put("🍟", true); + emojiTrie.put("🍕", true); + emojiTrie.put("🌭", true); + emojiTrie.put("🥪", true); + emojiTrie.put("🌮", true); + emojiTrie.put("🌯", true); + emojiTrie.put("🫔", true); + emojiTrie.put("🥙", true); + emojiTrie.put("🧆", true); + emojiTrie.put("🥚", true); + emojiTrie.put("🍳", true); + emojiTrie.put("🥘", true); + emojiTrie.put("🍲", true); + emojiTrie.put("🫕", true); + emojiTrie.put("🥣", true); + emojiTrie.put("🥗", true); + emojiTrie.put("🍿", true); + emojiTrie.put("🧈", true); + emojiTrie.put("🧂", true); + emojiTrie.put("🥫", true); + emojiTrie.put("🍱", true); + emojiTrie.put("🍘", true); + emojiTrie.put("🍙", true); + emojiTrie.put("🍚", true); + emojiTrie.put("🍛", true); + emojiTrie.put("🍜", true); + emojiTrie.put("🍝", true); + emojiTrie.put("🍠", true); + emojiTrie.put("🍢", true); + emojiTrie.put("🍣", true); + emojiTrie.put("🍤", true); + emojiTrie.put("🍥", true); + emojiTrie.put("🥮", true); + emojiTrie.put("🍡", true); + emojiTrie.put("🥟", true); + emojiTrie.put("🥠", true); + emojiTrie.put("🥡", true); + emojiTrie.put("🦀", true); + emojiTrie.put("🦞", true); + emojiTrie.put("🦐", true); + emojiTrie.put("🦑", true); + emojiTrie.put("🦪", true); + emojiTrie.put("🍦", true); + emojiTrie.put("🍧", true); + emojiTrie.put("🍨", true); + emojiTrie.put("🍩", true); + emojiTrie.put("🍪", true); + emojiTrie.put("🎂", true); + emojiTrie.put("🍰", true); + emojiTrie.put("🧁", true); + emojiTrie.put("🥧", true); + emojiTrie.put("🍫", true); + emojiTrie.put("🍬", true); + emojiTrie.put("🍭", true); + emojiTrie.put("🍮", true); + emojiTrie.put("🍯", true); + emojiTrie.put("🍼", true); + emojiTrie.put("🥛", true); + emojiTrie.put("☕", true); + emojiTrie.put("🫖", true); + emojiTrie.put("🍵", true); + emojiTrie.put("🍶", true); + emojiTrie.put("🍾", true); + emojiTrie.put("🍷", true); + emojiTrie.put("🍸", true); + emojiTrie.put("🍹", true); + emojiTrie.put("🍺", true); + emojiTrie.put("🍻", true); + emojiTrie.put("🥂", true); + emojiTrie.put("🥃", true); + emojiTrie.put("🫗", true); + emojiTrie.put("🥤", true); + emojiTrie.put("🧋", true); + emojiTrie.put("🧃", true); + emojiTrie.put("🧉", true); + emojiTrie.put("🧊", true); + emojiTrie.put("🥢", true); + emojiTrie.put("🍽️", true); + emojiTrie.put("🍽", true); + emojiTrie.put("🍴", true); + emojiTrie.put("🥄", true); + emojiTrie.put("🔪", true); + emojiTrie.put("🫙", true); + emojiTrie.put("🏺", true); + emojiTrie.put("🌍", true); + emojiTrie.put("🌎", true); + emojiTrie.put("🌏", true); + emojiTrie.put("🌐", true); + emojiTrie.put("🗺️", true); + emojiTrie.put("🗺", true); + emojiTrie.put("🗾", true); + emojiTrie.put("🧭", true); + emojiTrie.put("🏔️", true); + emojiTrie.put("🏔", true); + emojiTrie.put("⛰️", true); + emojiTrie.put("⛰", true); + emojiTrie.put("🌋", true); + emojiTrie.put("🗻", true); + emojiTrie.put("🏕️", true); + emojiTrie.put("🏕", true); + emojiTrie.put("🏖️", true); + emojiTrie.put("🏖", true); + emojiTrie.put("🏜️", true); + emojiTrie.put("🏜", true); + emojiTrie.put("🏝️", true); + emojiTrie.put("🏝", true); + emojiTrie.put("🏞️", true); + emojiTrie.put("🏞", true); + emojiTrie.put("🏟️", true); + emojiTrie.put("🏟", true); + emojiTrie.put("🏛️", true); + emojiTrie.put("🏛", true); + emojiTrie.put("🏗️", true); + emojiTrie.put("🏗", true); + emojiTrie.put("🧱", true); + emojiTrie.put("🪨", true); + emojiTrie.put("🪵", true); + emojiTrie.put("🛖", true); + emojiTrie.put("🏘️", true); + emojiTrie.put("🏘", true); + emojiTrie.put("🏚️", true); + emojiTrie.put("🏚", true); + emojiTrie.put("🏠", true); + emojiTrie.put("🏡", true); + emojiTrie.put("🏢", true); + emojiTrie.put("🏣", true); + emojiTrie.put("🏤", true); + emojiTrie.put("🏥", true); + emojiTrie.put("🏦", true); + emojiTrie.put("🏨", true); + emojiTrie.put("🏩", true); + emojiTrie.put("🏪", true); + emojiTrie.put("🏫", true); + emojiTrie.put("🏬", true); + emojiTrie.put("🏭", true); + emojiTrie.put("🏯", true); + emojiTrie.put("🏰", true); + emojiTrie.put("💒", true); + emojiTrie.put("🗼", true); + emojiTrie.put("🗽", true); + emojiTrie.put("⛪", true); + emojiTrie.put("🕌", true); + emojiTrie.put("🛕", true); + emojiTrie.put("🕍", true); + emojiTrie.put("⛩️", true); + emojiTrie.put("⛩", true); + emojiTrie.put("🕋", true); + emojiTrie.put("⛲", true); + emojiTrie.put("⛺", true); + emojiTrie.put("🌁", true); + emojiTrie.put("🌃", true); + emojiTrie.put("🏙️", true); + emojiTrie.put("🏙", true); + emojiTrie.put("🌄", true); + emojiTrie.put("🌅", true); + emojiTrie.put("🌆", true); + emojiTrie.put("🌇", true); + emojiTrie.put("🌉", true); + emojiTrie.put("♨️", true); + emojiTrie.put("♨", true); + emojiTrie.put("🎠", true); + emojiTrie.put("🛝", true); + emojiTrie.put("🎡", true); + emojiTrie.put("🎢", true); + emojiTrie.put("💈", true); + emojiTrie.put("🎪", true); + emojiTrie.put("🚂", true); + emojiTrie.put("🚃", true); + emojiTrie.put("🚄", true); + emojiTrie.put("🚅", true); + emojiTrie.put("🚆", true); + emojiTrie.put("🚇", true); + emojiTrie.put("🚈", true); + emojiTrie.put("🚉", true); + emojiTrie.put("🚊", true); + emojiTrie.put("🚝", true); + emojiTrie.put("🚞", true); + emojiTrie.put("🚋", true); + emojiTrie.put("🚌", true); + emojiTrie.put("🚍", true); + emojiTrie.put("🚎", true); + emojiTrie.put("🚐", true); + emojiTrie.put("🚑", true); + emojiTrie.put("🚒", true); + emojiTrie.put("🚓", true); + emojiTrie.put("🚔", true); + emojiTrie.put("🚕", true); + emojiTrie.put("🚖", true); + emojiTrie.put("🚗", true); + emojiTrie.put("🚘", true); + emojiTrie.put("🚙", true); + emojiTrie.put("🛻", true); + emojiTrie.put("🚚", true); + emojiTrie.put("🚛", true); + emojiTrie.put("🚜", true); + emojiTrie.put("🏎️", true); + emojiTrie.put("🏎", true); + emojiTrie.put("🏍️", true); + emojiTrie.put("🏍", true); + emojiTrie.put("🛵", true); + emojiTrie.put("🦽", true); + emojiTrie.put("🦼", true); + emojiTrie.put("🛺", true); + emojiTrie.put("🚲", true); + emojiTrie.put("🛴", true); + emojiTrie.put("🛹", true); + emojiTrie.put("🛼", true); + emojiTrie.put("🚏", true); + emojiTrie.put("🛣️", true); + emojiTrie.put("🛣", true); + emojiTrie.put("🛤️", true); + emojiTrie.put("🛤", true); + emojiTrie.put("🛢️", true); + emojiTrie.put("🛢", true); + emojiTrie.put("⛽", true); + emojiTrie.put("🛞", true); + emojiTrie.put("🚨", true); + emojiTrie.put("🚥", true); + emojiTrie.put("🚦", true); + emojiTrie.put("🛑", true); + emojiTrie.put("🚧", true); + emojiTrie.put("⚓", true); + emojiTrie.put("🛟", true); + emojiTrie.put("⛵", true); + emojiTrie.put("🛶", true); + emojiTrie.put("🚤", true); + emojiTrie.put("🛳️", true); + emojiTrie.put("🛳", true); + emojiTrie.put("⛴️", true); + emojiTrie.put("⛴", true); + emojiTrie.put("🛥️", true); + emojiTrie.put("🛥", true); + emojiTrie.put("🚢", true); + emojiTrie.put("✈️", true); + emojiTrie.put("✈", true); + emojiTrie.put("🛩️", true); + emojiTrie.put("🛩", true); + emojiTrie.put("🛫", true); + emojiTrie.put("🛬", true); + emojiTrie.put("🪂", true); + emojiTrie.put("💺", true); + emojiTrie.put("🚁", true); + emojiTrie.put("🚟", true); + emojiTrie.put("🚠", true); + emojiTrie.put("🚡", true); + emojiTrie.put("🛰️", true); + emojiTrie.put("🛰", true); + emojiTrie.put("🚀", true); + emojiTrie.put("🛸", true); + emojiTrie.put("🛎️", true); + emojiTrie.put("🛎", true); + emojiTrie.put("🧳", true); + emojiTrie.put("⌛", true); + emojiTrie.put("⏳", true); + emojiTrie.put("⌚", true); + emojiTrie.put("⏰", true); + emojiTrie.put("⏱️", true); + emojiTrie.put("⏱", true); + emojiTrie.put("⏲️", true); + emojiTrie.put("⏲", true); + emojiTrie.put("🕰️", true); + emojiTrie.put("🕰", true); + emojiTrie.put("🕛", true); + emojiTrie.put("🕧", true); + emojiTrie.put("🕐", true); + emojiTrie.put("🕜", true); + emojiTrie.put("🕑", true); + emojiTrie.put("🕝", true); + emojiTrie.put("🕒", true); + emojiTrie.put("🕞", true); + emojiTrie.put("🕓", true); + emojiTrie.put("🕟", true); + emojiTrie.put("🕔", true); + emojiTrie.put("🕠", true); + emojiTrie.put("🕕", true); + emojiTrie.put("🕡", true); + emojiTrie.put("🕖", true); + emojiTrie.put("🕢", true); + emojiTrie.put("🕗", true); + emojiTrie.put("🕣", true); + emojiTrie.put("🕘", true); + emojiTrie.put("🕤", true); + emojiTrie.put("🕙", true); + emojiTrie.put("🕥", true); + emojiTrie.put("🕚", true); + emojiTrie.put("🕦", true); + emojiTrie.put("🌑", true); + emojiTrie.put("🌒", true); + emojiTrie.put("🌓", true); + emojiTrie.put("🌔", true); + emojiTrie.put("🌕", true); + emojiTrie.put("🌖", true); + emojiTrie.put("🌗", true); + emojiTrie.put("🌘", true); + emojiTrie.put("🌙", true); + emojiTrie.put("🌚", true); + emojiTrie.put("🌛", true); + emojiTrie.put("🌜", true); + emojiTrie.put("🌡️", true); + emojiTrie.put("🌡", true); + emojiTrie.put("☀️", true); + emojiTrie.put("☀", true); + emojiTrie.put("🌝", true); + emojiTrie.put("🌞", true); + emojiTrie.put("🪐", true); + emojiTrie.put("⭐", true); + emojiTrie.put("🌟", true); + emojiTrie.put("🌠", true); + emojiTrie.put("🌌", true); + emojiTrie.put("☁️", true); + emojiTrie.put("☁", true); + emojiTrie.put("⛅", true); + emojiTrie.put("⛈️", true); + emojiTrie.put("⛈", true); + emojiTrie.put("🌤️", true); + emojiTrie.put("🌤", true); + emojiTrie.put("🌥️", true); + emojiTrie.put("🌥", true); + emojiTrie.put("🌦️", true); + emojiTrie.put("🌦", true); + emojiTrie.put("🌧️", true); + emojiTrie.put("🌧", true); + emojiTrie.put("🌨️", true); + emojiTrie.put("🌨", true); + emojiTrie.put("🌩️", true); + emojiTrie.put("🌩", true); + emojiTrie.put("🌪️", true); + emojiTrie.put("🌪", true); + emojiTrie.put("🌫️", true); + emojiTrie.put("🌫", true); + emojiTrie.put("🌬️", true); + emojiTrie.put("🌬", true); + emojiTrie.put("🌀", true); + emojiTrie.put("🌈", true); + emojiTrie.put("🌂", true); + emojiTrie.put("☂️", true); + emojiTrie.put("☂", true); + emojiTrie.put("☔", true); + emojiTrie.put("⛱️", true); + emojiTrie.put("⛱", true); + emojiTrie.put("⚡", true); + emojiTrie.put("❄️", true); + emojiTrie.put("❄", true); + emojiTrie.put("☃️", true); + emojiTrie.put("☃", true); + emojiTrie.put("⛄", true); + emojiTrie.put("☄️", true); + emojiTrie.put("☄", true); + emojiTrie.put("🔥", true); + emojiTrie.put("💧", true); + emojiTrie.put("🌊", true); + emojiTrie.put("🎃", true); + emojiTrie.put("🎄", true); + emojiTrie.put("🎆", true); + emojiTrie.put("🎇", true); + emojiTrie.put("🧨", true); + emojiTrie.put("✨", true); + emojiTrie.put("🎈", true); + emojiTrie.put("🎉", true); + emojiTrie.put("🎊", true); + emojiTrie.put("🎋", true); + emojiTrie.put("🎍", true); + emojiTrie.put("🎎", true); + emojiTrie.put("🎏", true); + emojiTrie.put("🎐", true); + emojiTrie.put("🎑", true); + emojiTrie.put("🧧", true); + emojiTrie.put("🎀", true); + emojiTrie.put("🎁", true); + emojiTrie.put("🎗️", true); + emojiTrie.put("🎗", true); + emojiTrie.put("🎟️", true); + emojiTrie.put("🎟", true); + emojiTrie.put("🎫", true); + emojiTrie.put("🎖️", true); + emojiTrie.put("🎖", true); + emojiTrie.put("🏆", true); + emojiTrie.put("🏅", true); + emojiTrie.put("🥇", true); + emojiTrie.put("🥈", true); + emojiTrie.put("🥉", true); + emojiTrie.put("⚽", true); + emojiTrie.put("⚾", true); + emojiTrie.put("🥎", true); + emojiTrie.put("🏀", true); + emojiTrie.put("🏐", true); + emojiTrie.put("🏈", true); + emojiTrie.put("🏉", true); + emojiTrie.put("🎾", true); + emojiTrie.put("🥏", true); + emojiTrie.put("🎳", true); + emojiTrie.put("🏏", true); + emojiTrie.put("🏑", true); + emojiTrie.put("🏒", true); + emojiTrie.put("🥍", true); + emojiTrie.put("🏓", true); + emojiTrie.put("🏸", true); + emojiTrie.put("🥊", true); + emojiTrie.put("🥋", true); + emojiTrie.put("🥅", true); + emojiTrie.put("⛳", true); + emojiTrie.put("⛸️", true); + emojiTrie.put("⛸", true); + emojiTrie.put("🎣", true); + emojiTrie.put("🤿", true); + emojiTrie.put("🎽", true); + emojiTrie.put("🎿", true); + emojiTrie.put("🛷", true); + emojiTrie.put("🥌", true); + emojiTrie.put("🎯", true); + emojiTrie.put("🪀", true); + emojiTrie.put("🪁", true); + emojiTrie.put("🔫", true); + emojiTrie.put("🎱", true); + emojiTrie.put("🔮", true); + emojiTrie.put("🪄", true); + emojiTrie.put("🎮", true); + emojiTrie.put("🕹️", true); + emojiTrie.put("🕹", true); + emojiTrie.put("🎰", true); + emojiTrie.put("🎲", true); + emojiTrie.put("🧩", true); + emojiTrie.put("🧸", true); + emojiTrie.put("🪅", true); + emojiTrie.put("🪩", true); + emojiTrie.put("🪆", true); + emojiTrie.put("♠️", true); + emojiTrie.put("♠", true); + emojiTrie.put("♥️", true); + emojiTrie.put("♥", true); + emojiTrie.put("♦️", true); + emojiTrie.put("♦", true); + emojiTrie.put("♣️", true); + emojiTrie.put("♣", true); + emojiTrie.put("♟️", true); + emojiTrie.put("♟", true); + emojiTrie.put("🃏", true); + emojiTrie.put("🀄", true); + emojiTrie.put("🎴", true); + emojiTrie.put("🎭", true); + emojiTrie.put("🖼️", true); + emojiTrie.put("🖼", true); + emojiTrie.put("🎨", true); + emojiTrie.put("🧵", true); + emojiTrie.put("🪡", true); + emojiTrie.put("🧶", true); + emojiTrie.put("🪢", true); + emojiTrie.put("👓", true); + emojiTrie.put("🕶️", true); + emojiTrie.put("🕶", true); + emojiTrie.put("🥽", true); + emojiTrie.put("🥼", true); + emojiTrie.put("🦺", true); + emojiTrie.put("👔", true); + emojiTrie.put("👕", true); + emojiTrie.put("👖", true); + emojiTrie.put("🧣", true); + emojiTrie.put("🧤", true); + emojiTrie.put("🧥", true); + emojiTrie.put("🧦", true); + emojiTrie.put("👗", true); + emojiTrie.put("👘", true); + emojiTrie.put("🥻", true); + emojiTrie.put("🩱", true); + emojiTrie.put("🩲", true); + emojiTrie.put("🩳", true); + emojiTrie.put("👙", true); + emojiTrie.put("👚", true); + emojiTrie.put("🪭", true); + emojiTrie.put("👛", true); + emojiTrie.put("👜", true); + emojiTrie.put("👝", true); + emojiTrie.put("🛍️", true); + emojiTrie.put("🛍", true); + emojiTrie.put("🎒", true); + emojiTrie.put("🩴", true); + emojiTrie.put("👞", true); + emojiTrie.put("👟", true); + emojiTrie.put("🥾", true); + emojiTrie.put("🥿", true); + emojiTrie.put("👠", true); + emojiTrie.put("👡", true); + emojiTrie.put("🩰", true); + emojiTrie.put("👢", true); + emojiTrie.put("🪮", true); + emojiTrie.put("👑", true); + emojiTrie.put("👒", true); + emojiTrie.put("🎩", true); + emojiTrie.put("🎓", true); + emojiTrie.put("🧢", true); + emojiTrie.put("🪖", true); + emojiTrie.put("⛑️", true); + emojiTrie.put("⛑", true); + emojiTrie.put("📿", true); + emojiTrie.put("💄", true); + emojiTrie.put("💍", true); + emojiTrie.put("💎", true); + emojiTrie.put("🔇", true); + emojiTrie.put("🔈", true); + emojiTrie.put("🔉", true); + emojiTrie.put("🔊", true); + emojiTrie.put("📢", true); + emojiTrie.put("📣", true); + emojiTrie.put("📯", true); + emojiTrie.put("🔔", true); + emojiTrie.put("🔕", true); + emojiTrie.put("🎼", true); + emojiTrie.put("🎵", true); + emojiTrie.put("🎶", true); + emojiTrie.put("🎙️", true); + emojiTrie.put("🎙", true); + emojiTrie.put("🎚️", true); + emojiTrie.put("🎚", true); + emojiTrie.put("🎛️", true); + emojiTrie.put("🎛", true); + emojiTrie.put("🎤", true); + emojiTrie.put("🎧", true); + emojiTrie.put("📻", true); + emojiTrie.put("🎷", true); + emojiTrie.put("🪗", true); + emojiTrie.put("🎸", true); + emojiTrie.put("🎹", true); + emojiTrie.put("🎺", true); + emojiTrie.put("🎻", true); + emojiTrie.put("🪕", true); + emojiTrie.put("🥁", true); + emojiTrie.put("🪘", true); + emojiTrie.put("🪇", true); + emojiTrie.put("🪈", true); + emojiTrie.put("📱", true); + emojiTrie.put("📲", true); + emojiTrie.put("☎️", true); + emojiTrie.put("☎", true); + emojiTrie.put("📞", true); + emojiTrie.put("📟", true); + emojiTrie.put("📠", true); + emojiTrie.put("🔋", true); + emojiTrie.put("🪫", true); + emojiTrie.put("🔌", true); + emojiTrie.put("💻", true); + emojiTrie.put("🖥️", true); + emojiTrie.put("🖥", true); + emojiTrie.put("🖨️", true); + emojiTrie.put("🖨", true); + emojiTrie.put("⌨️", true); + emojiTrie.put("⌨", true); + emojiTrie.put("🖱️", true); + emojiTrie.put("🖱", true); + emojiTrie.put("🖲️", true); + emojiTrie.put("🖲", true); + emojiTrie.put("💽", true); + emojiTrie.put("💾", true); + emojiTrie.put("💿", true); + emojiTrie.put("📀", true); + emojiTrie.put("🧮", true); + emojiTrie.put("🎥", true); + emojiTrie.put("🎞️", true); + emojiTrie.put("🎞", true); + emojiTrie.put("📽️", true); + emojiTrie.put("📽", true); + emojiTrie.put("🎬", true); + emojiTrie.put("📺", true); + emojiTrie.put("📷", true); + emojiTrie.put("📸", true); + emojiTrie.put("📹", true); + emojiTrie.put("📼", true); + emojiTrie.put("🔍", true); + emojiTrie.put("🔎", true); + emojiTrie.put("🕯️", true); + emojiTrie.put("🕯", true); + emojiTrie.put("💡", true); + emojiTrie.put("🔦", true); + emojiTrie.put("🏮", true); + emojiTrie.put("🪔", true); + emojiTrie.put("📔", true); + emojiTrie.put("📕", true); + emojiTrie.put("📖", true); + emojiTrie.put("📗", true); + emojiTrie.put("📘", true); + emojiTrie.put("📙", true); + emojiTrie.put("📚", true); + emojiTrie.put("📓", true); + emojiTrie.put("📒", true); + emojiTrie.put("📃", true); + emojiTrie.put("📜", true); + emojiTrie.put("📄", true); + emojiTrie.put("📰", true); + emojiTrie.put("🗞️", true); + emojiTrie.put("🗞", true); + emojiTrie.put("📑", true); + emojiTrie.put("🔖", true); + emojiTrie.put("🏷️", true); + emojiTrie.put("🏷", true); + emojiTrie.put("💰", true); + emojiTrie.put("🪙", true); + emojiTrie.put("💴", true); + emojiTrie.put("💵", true); + emojiTrie.put("💶", true); + emojiTrie.put("💷", true); + emojiTrie.put("💸", true); + emojiTrie.put("💳", true); + emojiTrie.put("🧾", true); + emojiTrie.put("💹", true); + emojiTrie.put("✉️", true); + emojiTrie.put("✉", true); + emojiTrie.put("📧", true); + emojiTrie.put("📨", true); + emojiTrie.put("📩", true); + emojiTrie.put("📤", true); + emojiTrie.put("📥", true); + emojiTrie.put("📦", true); + emojiTrie.put("📫", true); + emojiTrie.put("📪", true); + emojiTrie.put("📬", true); + emojiTrie.put("📭", true); + emojiTrie.put("📮", true); + emojiTrie.put("🗳️", true); + emojiTrie.put("🗳", true); + emojiTrie.put("✏️", true); + emojiTrie.put("✏", true); + emojiTrie.put("✒️", true); + emojiTrie.put("✒", true); + emojiTrie.put("🖋️", true); + emojiTrie.put("🖋", true); + emojiTrie.put("🖊️", true); + emojiTrie.put("🖊", true); + emojiTrie.put("🖌️", true); + emojiTrie.put("🖌", true); + emojiTrie.put("🖍️", true); + emojiTrie.put("🖍", true); + emojiTrie.put("📝", true); + emojiTrie.put("💼", true); + emojiTrie.put("📁", true); + emojiTrie.put("📂", true); + emojiTrie.put("🗂️", true); + emojiTrie.put("🗂", true); + emojiTrie.put("📅", true); + emojiTrie.put("📆", true); + emojiTrie.put("🗒️", true); + emojiTrie.put("🗒", true); + emojiTrie.put("🗓️", true); + emojiTrie.put("🗓", true); + emojiTrie.put("📇", true); + emojiTrie.put("📈", true); + emojiTrie.put("📉", true); + emojiTrie.put("📊", true); + emojiTrie.put("📋", true); + emojiTrie.put("📌", true); + emojiTrie.put("📍", true); + emojiTrie.put("📎", true); + emojiTrie.put("🖇️", true); + emojiTrie.put("🖇", true); + emojiTrie.put("📏", true); + emojiTrie.put("📐", true); + emojiTrie.put("✂️", true); + emojiTrie.put("✂", true); + emojiTrie.put("🗃️", true); + emojiTrie.put("🗃", true); + emojiTrie.put("🗄️", true); + emojiTrie.put("🗄", true); + emojiTrie.put("🗑️", true); + emojiTrie.put("🗑", true); + emojiTrie.put("🔒", true); + emojiTrie.put("🔓", true); + emojiTrie.put("🔏", true); + emojiTrie.put("🔐", true); + emojiTrie.put("🔑", true); + emojiTrie.put("🗝️", true); + emojiTrie.put("🗝", true); + emojiTrie.put("🔨", true); + emojiTrie.put("🪓", true); + emojiTrie.put("⛏️", true); + emojiTrie.put("⛏", true); + emojiTrie.put("⚒️", true); + emojiTrie.put("⚒", true); + emojiTrie.put("🛠️", true); + emojiTrie.put("🛠", true); + emojiTrie.put("🗡️", true); + emojiTrie.put("🗡", true); + emojiTrie.put("⚔️", true); + emojiTrie.put("⚔", true); + emojiTrie.put("💣", true); + emojiTrie.put("🪃", true); + emojiTrie.put("🏹", true); + emojiTrie.put("🛡️", true); + emojiTrie.put("🛡", true); + emojiTrie.put("🪚", true); + emojiTrie.put("🔧", true); + emojiTrie.put("🪛", true); + emojiTrie.put("🔩", true); + emojiTrie.put("⚙️", true); + emojiTrie.put("⚙", true); + emojiTrie.put("🗜️", true); + emojiTrie.put("🗜", true); + emojiTrie.put("⚖️", true); + emojiTrie.put("⚖", true); + emojiTrie.put("🦯", true); + emojiTrie.put("🔗", true); + emojiTrie.put("⛓️", true); + emojiTrie.put("⛓", true); + emojiTrie.put("🪝", true); + emojiTrie.put("🧰", true); + emojiTrie.put("🧲", true); + emojiTrie.put("🪜", true); + emojiTrie.put("⚗️", true); + emojiTrie.put("⚗", true); + emojiTrie.put("🧪", true); + emojiTrie.put("🧫", true); + emojiTrie.put("🧬", true); + emojiTrie.put("🔬", true); + emojiTrie.put("🔭", true); + emojiTrie.put("📡", true); + emojiTrie.put("💉", true); + emojiTrie.put("🩸", true); + emojiTrie.put("💊", true); + emojiTrie.put("🩹", true); + emojiTrie.put("🩼", true); + emojiTrie.put("🩺", true); + emojiTrie.put("🩻", true); + emojiTrie.put("🚪", true); + emojiTrie.put("🛗", true); + emojiTrie.put("🪞", true); + emojiTrie.put("🪟", true); + emojiTrie.put("🛏️", true); + emojiTrie.put("🛏", true); + emojiTrie.put("🛋️", true); + emojiTrie.put("🛋", true); + emojiTrie.put("🪑", true); + emojiTrie.put("🚽", true); + emojiTrie.put("🪠", true); + emojiTrie.put("🚿", true); + emojiTrie.put("🛁", true); + emojiTrie.put("🪤", true); + emojiTrie.put("🪒", true); + emojiTrie.put("🧴", true); + emojiTrie.put("🧷", true); + emojiTrie.put("🧹", true); + emojiTrie.put("🧺", true); + emojiTrie.put("🧻", true); + emojiTrie.put("🪣", true); + emojiTrie.put("🧼", true); + emojiTrie.put("🫧", true); + emojiTrie.put("🪥", true); + emojiTrie.put("🧽", true); + emojiTrie.put("🧯", true); + emojiTrie.put("🛒", true); + emojiTrie.put("🚬", true); + emojiTrie.put("⚰️", true); + emojiTrie.put("⚰", true); + emojiTrie.put("🪦", true); + emojiTrie.put("⚱️", true); + emojiTrie.put("⚱", true); + emojiTrie.put("🧿", true); + emojiTrie.put("🪬", true); + emojiTrie.put("🗿", true); + emojiTrie.put("🪧", true); + emojiTrie.put("🪪", true); + emojiTrie.put("🏧", true); + emojiTrie.put("🚮", true); + emojiTrie.put("🚰", true); + emojiTrie.put("♿", true); + emojiTrie.put("🚹", true); + emojiTrie.put("🚺", true); + emojiTrie.put("🚻", true); + emojiTrie.put("🚼", true); + emojiTrie.put("🚾", true); + emojiTrie.put("🛂", true); + emojiTrie.put("🛃", true); + emojiTrie.put("🛄", true); + emojiTrie.put("🛅", true); + emojiTrie.put("⚠️", true); + emojiTrie.put("⚠", true); + emojiTrie.put("🚸", true); + emojiTrie.put("⛔", true); + emojiTrie.put("🚫", true); + emojiTrie.put("🚳", true); + emojiTrie.put("🚭", true); + emojiTrie.put("🚯", true); + emojiTrie.put("🚱", true); + emojiTrie.put("🚷", true); + emojiTrie.put("📵", true); + emojiTrie.put("🔞", true); + emojiTrie.put("☢️", true); + emojiTrie.put("☢", true); + emojiTrie.put("☣️", true); + emojiTrie.put("☣", true); + emojiTrie.put("⬆️", true); + emojiTrie.put("⬆", true); + emojiTrie.put("↗️", true); + emojiTrie.put("↗", true); + emojiTrie.put("➡️", true); + emojiTrie.put("➡", true); + emojiTrie.put("↘️", true); + emojiTrie.put("↘", true); + emojiTrie.put("⬇️", true); + emojiTrie.put("⬇", true); + emojiTrie.put("↙️", true); + emojiTrie.put("↙", true); + emojiTrie.put("⬅️", true); + emojiTrie.put("⬅", true); + emojiTrie.put("↖️", true); + emojiTrie.put("↖", true); + emojiTrie.put("↕️", true); + emojiTrie.put("↕", true); + emojiTrie.put("↔️", true); + emojiTrie.put("↔", true); + emojiTrie.put("↩️", true); + emojiTrie.put("↩", true); + emojiTrie.put("↪️", true); + emojiTrie.put("↪", true); + emojiTrie.put("⤴️", true); + emojiTrie.put("⤴", true); + emojiTrie.put("⤵️", true); + emojiTrie.put("⤵", true); + emojiTrie.put("🔃", true); + emojiTrie.put("🔄", true); + emojiTrie.put("🔙", true); + emojiTrie.put("🔚", true); + emojiTrie.put("🔛", true); + emojiTrie.put("🔜", true); + emojiTrie.put("🔝", true); + emojiTrie.put("🛐", true); + emojiTrie.put("⚛️", true); + emojiTrie.put("⚛", true); + emojiTrie.put("🕉️", true); + emojiTrie.put("🕉", true); + emojiTrie.put("✡️", true); + emojiTrie.put("✡", true); + emojiTrie.put("☸️", true); + emojiTrie.put("☸", true); + emojiTrie.put("☯️", true); + emojiTrie.put("☯", true); + emojiTrie.put("✝️", true); + emojiTrie.put("✝", true); + emojiTrie.put("☦️", true); + emojiTrie.put("☦", true); + emojiTrie.put("☪️", true); + emojiTrie.put("☪", true); + emojiTrie.put("☮️", true); + emojiTrie.put("☮", true); + emojiTrie.put("🕎", true); + emojiTrie.put("🔯", true); + emojiTrie.put("🪯", true); + emojiTrie.put("♈", true); + emojiTrie.put("♉", true); + emojiTrie.put("♊", true); + emojiTrie.put("♋", true); + emojiTrie.put("♌", true); + emojiTrie.put("♍", true); + emojiTrie.put("♎", true); + emojiTrie.put("♏", true); + emojiTrie.put("♐", true); + emojiTrie.put("♑", true); + emojiTrie.put("♒", true); + emojiTrie.put("♓", true); + emojiTrie.put("⛎", true); + emojiTrie.put("🔀", true); + emojiTrie.put("🔁", true); + emojiTrie.put("🔂", true); + emojiTrie.put("▶️", true); + emojiTrie.put("▶", true); + emojiTrie.put("⏩", true); + emojiTrie.put("⏭️", true); + emojiTrie.put("⏭", true); + emojiTrie.put("⏯️", true); + emojiTrie.put("⏯", true); + emojiTrie.put("◀️", true); + emojiTrie.put("◀", true); + emojiTrie.put("⏪", true); + emojiTrie.put("⏮️", true); + emojiTrie.put("⏮", true); + emojiTrie.put("🔼", true); + emojiTrie.put("⏫", true); + emojiTrie.put("🔽", true); + emojiTrie.put("⏬", true); + emojiTrie.put("⏸️", true); + emojiTrie.put("⏸", true); + emojiTrie.put("⏹️", true); + emojiTrie.put("⏹", true); + emojiTrie.put("⏺️", true); + emojiTrie.put("⏺", true); + emojiTrie.put("⏏️", true); + emojiTrie.put("⏏", true); + emojiTrie.put("🎦", true); + emojiTrie.put("🔅", true); + emojiTrie.put("🔆", true); + emojiTrie.put("📶", true); + emojiTrie.put("🛜", true); + emojiTrie.put("📳", true); + emojiTrie.put("📴", true); + emojiTrie.put("♀️", true); + emojiTrie.put("♀", true); + emojiTrie.put("♂️", true); + emojiTrie.put("♂", true); + emojiTrie.put("⚧️", true); + emojiTrie.put("⚧", true); + emojiTrie.put("✖️", true); + emojiTrie.put("✖", true); + emojiTrie.put("➕", true); + emojiTrie.put("➖", true); + emojiTrie.put("➗", true); + emojiTrie.put("🟰", true); + emojiTrie.put("♾️", true); + emojiTrie.put("♾", true); + emojiTrie.put("‼️", true); + emojiTrie.put("‼", true); + emojiTrie.put("⁉️", true); + emojiTrie.put("⁉", true); + emojiTrie.put("❓", true); + emojiTrie.put("❔", true); + emojiTrie.put("❕", true); + emojiTrie.put("❗", true); + emojiTrie.put("〰️", true); + emojiTrie.put("〰", true); + emojiTrie.put("💱", true); + emojiTrie.put("💲", true); + emojiTrie.put("⚕️", true); + emojiTrie.put("⚕", true); + emojiTrie.put("♻️", true); + emojiTrie.put("♻", true); + emojiTrie.put("⚜️", true); + emojiTrie.put("⚜", true); + emojiTrie.put("🔱", true); + emojiTrie.put("📛", true); + emojiTrie.put("🔰", true); + emojiTrie.put("⭕", true); + emojiTrie.put("✅", true); + emojiTrie.put("☑️", true); + emojiTrie.put("☑", true); + emojiTrie.put("✔️", true); + emojiTrie.put("✔", true); + emojiTrie.put("❌", true); + emojiTrie.put("❎", true); + emojiTrie.put("➰", true); + emojiTrie.put("➿", true); + emojiTrie.put("〽️", true); + emojiTrie.put("〽", true); + emojiTrie.put("✳️", true); + emojiTrie.put("✳", true); + emojiTrie.put("✴️", true); + emojiTrie.put("✴", true); + emojiTrie.put("❇️", true); + emojiTrie.put("❇", true); + emojiTrie.put("©️", true); + emojiTrie.put("©", true); + emojiTrie.put("®️", true); + emojiTrie.put("®", true); + emojiTrie.put("™️", true); + emojiTrie.put("™", true); + emojiTrie.put("#️⃣", true); + emojiTrie.put("#⃣", true); + emojiTrie.put("*️⃣", true); + emojiTrie.put("*⃣", true); + emojiTrie.put("0️⃣", true); + emojiTrie.put("0⃣", true); + emojiTrie.put("1️⃣", true); + emojiTrie.put("1⃣", true); + emojiTrie.put("2️⃣", true); + emojiTrie.put("2⃣", true); + emojiTrie.put("3️⃣", true); + emojiTrie.put("3⃣", true); + emojiTrie.put("4️⃣", true); + emojiTrie.put("4⃣", true); + emojiTrie.put("5️⃣", true); + emojiTrie.put("5⃣", true); + emojiTrie.put("6️⃣", true); + emojiTrie.put("6⃣", true); + emojiTrie.put("7️⃣", true); + emojiTrie.put("7⃣", true); + emojiTrie.put("8️⃣", true); + emojiTrie.put("8⃣", true); + emojiTrie.put("9️⃣", true); + emojiTrie.put("9⃣", true); + emojiTrie.put("🔟", true); + emojiTrie.put("🔠", true); + emojiTrie.put("🔡", true); + emojiTrie.put("🔢", true); + emojiTrie.put("🔣", true); + emojiTrie.put("🔤", true); + emojiTrie.put("🅰️", true); + emojiTrie.put("🅰", true); + emojiTrie.put("🆎", true); + emojiTrie.put("🅱️", true); + emojiTrie.put("🅱", true); + emojiTrie.put("🆑", true); + emojiTrie.put("🆒", true); + emojiTrie.put("🆓", true); + emojiTrie.put("ℹ️", true); + emojiTrie.put("ℹ", true); + emojiTrie.put("🆔", true); + emojiTrie.put("Ⓜ️", true); + emojiTrie.put("Ⓜ", true); + emojiTrie.put("🆕", true); + emojiTrie.put("🆖", true); + emojiTrie.put("🅾️", true); + emojiTrie.put("🅾", true); + emojiTrie.put("🆗", true); + emojiTrie.put("🅿️", true); + emojiTrie.put("🅿", true); + emojiTrie.put("🆘", true); + emojiTrie.put("🆙", true); + emojiTrie.put("🆚", true); + emojiTrie.put("🈁", true); + emojiTrie.put("🈂️", true); + emojiTrie.put("🈂", true); + emojiTrie.put("🈷️", true); + emojiTrie.put("🈷", true); + emojiTrie.put("🈶", true); + emojiTrie.put("🈯", true); + emojiTrie.put("🉐", true); + emojiTrie.put("🈹", true); + emojiTrie.put("🈚", true); + emojiTrie.put("🈲", true); + emojiTrie.put("🉑", true); + emojiTrie.put("🈸", true); + emojiTrie.put("🈴", true); + emojiTrie.put("🈳", true); + emojiTrie.put("㊗️", true); + emojiTrie.put("㊗", true); + emojiTrie.put("㊙️", true); + emojiTrie.put("㊙", true); + emojiTrie.put("🈺", true); + emojiTrie.put("🈵", true); + emojiTrie.put("🔴", true); + emojiTrie.put("🟠", true); + emojiTrie.put("🟡", true); + emojiTrie.put("🟢", true); + emojiTrie.put("🔵", true); + emojiTrie.put("🟣", true); + emojiTrie.put("🟤", true); + emojiTrie.put("⚫", true); + emojiTrie.put("⚪", true); + emojiTrie.put("🟥", true); + emojiTrie.put("🟧", true); + emojiTrie.put("🟨", true); + emojiTrie.put("🟩", true); + emojiTrie.put("🟦", true); + emojiTrie.put("🟪", true); + emojiTrie.put("🟫", true); + emojiTrie.put("⬛", true); + emojiTrie.put("⬜", true); + emojiTrie.put("◼️", true); + emojiTrie.put("◼", true); + emojiTrie.put("◻️", true); + emojiTrie.put("◻", true); + emojiTrie.put("◾", true); + emojiTrie.put("◽", true); + emojiTrie.put("▪️", true); + emojiTrie.put("▪", true); + emojiTrie.put("▫️", true); + emojiTrie.put("▫", true); + emojiTrie.put("🔶", true); + emojiTrie.put("🔷", true); + emojiTrie.put("🔸", true); + emojiTrie.put("🔹", true); + emojiTrie.put("🔺", true); + emojiTrie.put("🔻", true); + emojiTrie.put("💠", true); + emojiTrie.put("🔘", true); + emojiTrie.put("🔳", true); + emojiTrie.put("🔲", true); + emojiTrie.put("🏁", true); + emojiTrie.put("🚩", true); + emojiTrie.put("🎌", true); + emojiTrie.put("🏴", true); + emojiTrie.put("🏳️", true); + emojiTrie.put("🏳", true); + emojiTrie.put("🏳️‍🌈", true); + emojiTrie.put("🏳‍🌈", true); + emojiTrie.put("🏳️‍⚧️", true); + emojiTrie.put("🏳‍⚧️", true); + emojiTrie.put("🏳️‍⚧", true); + emojiTrie.put("🏳‍⚧", true); + emojiTrie.put("🏴‍☠️", true); + emojiTrie.put("🏴‍☠", true); + emojiTrie.put("🇦🇨", true); + emojiTrie.put("🇦🇩", true); + emojiTrie.put("🇦🇪", true); + emojiTrie.put("🇦🇫", true); + emojiTrie.put("🇦🇬", true); + emojiTrie.put("🇦🇮", true); + emojiTrie.put("🇦🇱", true); + emojiTrie.put("🇦🇲", true); + emojiTrie.put("🇦🇴", true); + emojiTrie.put("🇦🇶", true); + emojiTrie.put("🇦🇷", true); + emojiTrie.put("🇦🇸", true); + emojiTrie.put("🇦🇹", true); + emojiTrie.put("🇦🇺", true); + emojiTrie.put("🇦🇼", true); + emojiTrie.put("🇦🇽", true); + emojiTrie.put("🇦🇿", true); + emojiTrie.put("🇧🇦", true); + emojiTrie.put("🇧🇧", true); + emojiTrie.put("🇧🇩", true); + emojiTrie.put("🇧🇪", true); + emojiTrie.put("🇧🇫", true); + emojiTrie.put("🇧🇬", true); + emojiTrie.put("🇧🇭", true); + emojiTrie.put("🇧🇮", true); + emojiTrie.put("🇧🇯", true); + emojiTrie.put("🇧🇱", true); + emojiTrie.put("🇧🇲", true); + emojiTrie.put("🇧🇳", true); + emojiTrie.put("🇧🇴", true); + emojiTrie.put("🇧🇶", true); + emojiTrie.put("🇧🇷", true); + emojiTrie.put("🇧🇸", true); + emojiTrie.put("🇧🇹", true); + emojiTrie.put("🇧🇻", true); + emojiTrie.put("🇧🇼", true); + emojiTrie.put("🇧🇾", true); + emojiTrie.put("🇧🇿", true); + emojiTrie.put("🇨🇦", true); + emojiTrie.put("🇨🇨", true); + emojiTrie.put("🇨🇩", true); + emojiTrie.put("🇨🇫", true); + emojiTrie.put("🇨🇬", true); + emojiTrie.put("🇨🇭", true); + emojiTrie.put("🇨🇮", true); + emojiTrie.put("🇨🇰", true); + emojiTrie.put("🇨🇱", true); + emojiTrie.put("🇨🇲", true); + emojiTrie.put("🇨🇳", true); + emojiTrie.put("🇨🇴", true); + emojiTrie.put("🇨🇵", true); + emojiTrie.put("🇨🇷", true); + emojiTrie.put("🇨🇺", true); + emojiTrie.put("🇨🇻", true); + emojiTrie.put("🇨🇼", true); + emojiTrie.put("🇨🇽", true); + emojiTrie.put("🇨🇾", true); + emojiTrie.put("🇨🇿", true); + emojiTrie.put("🇩🇪", true); + emojiTrie.put("🇩🇬", true); + emojiTrie.put("🇩🇯", true); + emojiTrie.put("🇩🇰", true); + emojiTrie.put("🇩🇲", true); + emojiTrie.put("🇩🇴", true); + emojiTrie.put("🇩🇿", true); + emojiTrie.put("🇪🇦", true); + emojiTrie.put("🇪🇨", true); + emojiTrie.put("🇪🇪", true); + emojiTrie.put("🇪🇬", true); + emojiTrie.put("🇪🇭", true); + emojiTrie.put("🇪🇷", true); + emojiTrie.put("🇪🇸", true); + emojiTrie.put("🇪🇹", true); + emojiTrie.put("🇪🇺", true); + emojiTrie.put("🇫🇮", true); + emojiTrie.put("🇫🇯", true); + emojiTrie.put("🇫🇰", true); + emojiTrie.put("🇫🇲", true); + emojiTrie.put("🇫🇴", true); + emojiTrie.put("🇫🇷", true); + emojiTrie.put("🇬🇦", true); + emojiTrie.put("🇬🇧", true); + emojiTrie.put("🇬🇩", true); + emojiTrie.put("🇬🇪", true); + emojiTrie.put("🇬🇫", true); + emojiTrie.put("🇬🇬", true); + emojiTrie.put("🇬🇭", true); + emojiTrie.put("🇬🇮", true); + emojiTrie.put("🇬🇱", true); + emojiTrie.put("🇬🇲", true); + emojiTrie.put("🇬🇳", true); + emojiTrie.put("🇬🇵", true); + emojiTrie.put("🇬🇶", true); + emojiTrie.put("🇬🇷", true); + emojiTrie.put("🇬🇸", true); + emojiTrie.put("🇬🇹", true); + emojiTrie.put("🇬🇺", true); + emojiTrie.put("🇬🇼", true); + emojiTrie.put("🇬🇾", true); + emojiTrie.put("🇭🇰", true); + emojiTrie.put("🇭🇲", true); + emojiTrie.put("🇭🇳", true); + emojiTrie.put("🇭🇷", true); + emojiTrie.put("🇭🇹", true); + emojiTrie.put("🇭🇺", true); + emojiTrie.put("🇮🇨", true); + emojiTrie.put("🇮🇩", true); + emojiTrie.put("🇮🇪", true); + emojiTrie.put("🇮🇱", true); + emojiTrie.put("🇮🇲", true); + emojiTrie.put("🇮🇳", true); + emojiTrie.put("🇮🇴", true); + emojiTrie.put("🇮🇶", true); + emojiTrie.put("🇮🇷", true); + emojiTrie.put("🇮🇸", true); + emojiTrie.put("🇮🇹", true); + emojiTrie.put("🇯🇪", true); + emojiTrie.put("🇯🇲", true); + emojiTrie.put("🇯🇴", true); + emojiTrie.put("🇯🇵", true); + emojiTrie.put("🇰🇪", true); + emojiTrie.put("🇰🇬", true); + emojiTrie.put("🇰🇭", true); + emojiTrie.put("🇰🇮", true); + emojiTrie.put("🇰🇲", true); + emojiTrie.put("🇰🇳", true); + emojiTrie.put("🇰🇵", true); + emojiTrie.put("🇰🇷", true); + emojiTrie.put("🇰🇼", true); + emojiTrie.put("🇰🇾", true); + emojiTrie.put("🇰🇿", true); + emojiTrie.put("🇱🇦", true); + emojiTrie.put("🇱🇧", true); + emojiTrie.put("🇱🇨", true); + emojiTrie.put("🇱🇮", true); + emojiTrie.put("🇱🇰", true); + emojiTrie.put("🇱🇷", true); + emojiTrie.put("🇱🇸", true); + emojiTrie.put("🇱🇹", true); + emojiTrie.put("🇱🇺", true); + emojiTrie.put("🇱🇻", true); + emojiTrie.put("🇱🇾", true); + emojiTrie.put("🇲🇦", true); + emojiTrie.put("🇲🇨", true); + emojiTrie.put("🇲🇩", true); + emojiTrie.put("🇲🇪", true); + emojiTrie.put("🇲🇫", true); + emojiTrie.put("🇲🇬", true); + emojiTrie.put("🇲🇭", true); + emojiTrie.put("🇲🇰", true); + emojiTrie.put("🇲🇱", true); + emojiTrie.put("🇲🇲", true); + emojiTrie.put("🇲🇳", true); + emojiTrie.put("🇲🇴", true); + emojiTrie.put("🇲🇵", true); + emojiTrie.put("🇲🇶", true); + emojiTrie.put("🇲🇷", true); + emojiTrie.put("🇲🇸", true); + emojiTrie.put("🇲🇹", true); + emojiTrie.put("🇲🇺", true); + emojiTrie.put("🇲🇻", true); + emojiTrie.put("🇲🇼", true); + emojiTrie.put("🇲🇽", true); + emojiTrie.put("🇲🇾", true); + emojiTrie.put("🇲🇿", true); + emojiTrie.put("🇳🇦", true); + emojiTrie.put("🇳🇨", true); + emojiTrie.put("🇳🇪", true); + emojiTrie.put("🇳🇫", true); + emojiTrie.put("🇳🇬", true); + emojiTrie.put("🇳🇮", true); + emojiTrie.put("🇳🇱", true); + emojiTrie.put("🇳🇴", true); + emojiTrie.put("🇳🇵", true); + emojiTrie.put("🇳🇷", true); + emojiTrie.put("🇳🇺", true); + emojiTrie.put("🇳🇿", true); + emojiTrie.put("🇴🇲", true); + emojiTrie.put("🇵🇦", true); + emojiTrie.put("🇵🇪", true); + emojiTrie.put("🇵🇫", true); + emojiTrie.put("🇵🇬", true); + emojiTrie.put("🇵🇭", true); + emojiTrie.put("🇵🇰", true); + emojiTrie.put("🇵🇱", true); + emojiTrie.put("🇵🇲", true); + emojiTrie.put("🇵🇳", true); + emojiTrie.put("🇵🇷", true); + emojiTrie.put("🇵🇸", true); + emojiTrie.put("🇵🇹", true); + emojiTrie.put("🇵🇼", true); + emojiTrie.put("🇵🇾", true); + emojiTrie.put("🇶🇦", true); + emojiTrie.put("🇷🇪", true); + emojiTrie.put("🇷🇴", true); + emojiTrie.put("🇷🇸", true); + emojiTrie.put("🇷🇺", true); + emojiTrie.put("🇷🇼", true); + emojiTrie.put("🇸🇦", true); + emojiTrie.put("🇸🇧", true); + emojiTrie.put("🇸🇨", true); + emojiTrie.put("🇸🇩", true); + emojiTrie.put("🇸🇪", true); + emojiTrie.put("🇸🇬", true); + emojiTrie.put("🇸🇭", true); + emojiTrie.put("🇸🇮", true); + emojiTrie.put("🇸🇯", true); + emojiTrie.put("🇸🇰", true); + emojiTrie.put("🇸🇱", true); + emojiTrie.put("🇸🇲", true); + emojiTrie.put("🇸🇳", true); + emojiTrie.put("🇸🇴", true); + emojiTrie.put("🇸🇷", true); + emojiTrie.put("🇸🇸", true); + emojiTrie.put("🇸🇹", true); + emojiTrie.put("🇸🇻", true); + emojiTrie.put("🇸🇽", true); + emojiTrie.put("🇸🇾", true); + emojiTrie.put("🇸🇿", true); + emojiTrie.put("🇹🇦", true); + emojiTrie.put("🇹🇨", true); + emojiTrie.put("🇹🇩", true); + emojiTrie.put("🇹🇫", true); + emojiTrie.put("🇹🇬", true); + emojiTrie.put("🇹🇭", true); + emojiTrie.put("🇹🇯", true); + emojiTrie.put("🇹🇰", true); + emojiTrie.put("🇹🇱", true); + emojiTrie.put("🇹🇲", true); + emojiTrie.put("🇹🇳", true); + emojiTrie.put("🇹🇴", true); + emojiTrie.put("🇹🇷", true); + emojiTrie.put("🇹🇹", true); + emojiTrie.put("🇹🇻", true); + emojiTrie.put("🇹🇼", true); + emojiTrie.put("🇹🇿", true); + emojiTrie.put("🇺🇦", true); + emojiTrie.put("🇺🇬", true); + emojiTrie.put("🇺🇲", true); + emojiTrie.put("🇺🇳", true); + emojiTrie.put("🇺🇸", true); + emojiTrie.put("🇺🇾", true); + emojiTrie.put("🇺🇿", true); + emojiTrie.put("🇻🇦", true); + emojiTrie.put("🇻🇨", true); + emojiTrie.put("🇻🇪", true); + emojiTrie.put("🇻🇬", true); + emojiTrie.put("🇻🇮", true); + emojiTrie.put("🇻🇳", true); + emojiTrie.put("🇻🇺", true); + emojiTrie.put("🇼🇫", true); + emojiTrie.put("🇼🇸", true); + emojiTrie.put("🇽🇰", true); + emojiTrie.put("🇾🇪", true); + emojiTrie.put("🇾🇹", true); + emojiTrie.put("🇿🇦", true); + emojiTrie.put("🇿🇲", true); + emojiTrie.put("🇿🇼", true); + emojiTrie.put("🏴󠁧󠁢󠁥󠁮󠁧󠁿", true); + emojiTrie.put("🏴󠁧󠁢󠁳󠁣󠁴󠁿", true); + emojiTrie.put("🏴󠁧󠁢󠁷󠁬󠁳󠁿", true); + } + + private Emojis() { + } + + /** + * Checks whether the given {@code string} contains a single emoji. + *

+ * Use {@link #onlyContainsEmojis(String)} to check if a string consists only of (multiple) emojis. + * + * @param string the string to check + * @return true if the string contains a single emoji, false otherwise + * @see #onlyContainsEmojis(String) + */ + public static boolean isEmoji(String string) { + return emojiTrie.get(string) != null; + } + + /** + * Checks whether the given {@code string} contains at least one emoji. + * + * @param string the string to check + * @return true if the string contains at least one emoji, false otherwise + */ + public static boolean containsEmoji(String string) { + Trie.ContainmentIterator iterator = emojiTrie.iterator(); + int[] sequence = string.codePoints().toArray(); + + for (int index = 0; index < sequence.length; index++) { + if (!iterator.doContinue(sequence[index])) { + if (iterator.isCompleted()) { + return true; + } + iterator.resetWith(sequence[index]); + } + } + + return iterator.isCompleted(); + } + + /** + * Checks whether the given {@code string} contains only emojis. + *

+ * Use {@link #isEmoji(String)} to check if a string contains a single emoji. + * + * @param string the string to check + * @return true if the string contains only emojis, false otherwise + * @see #isEmoji(String) + */ + public static boolean onlyContainsEmojis(String string) { + Trie.ContainmentIterator iterator = emojiTrie.iterator(); + int[] sequence = string.codePoints().toArray(); + + for (int index = 0; index < sequence.length; index++) { + if (!iterator.doContinue(sequence[index])) { + if (!iterator.isCompleted()) { + return false; + } + if (!iterator.resetWith(sequence[index])) { + return false; + } + } + } + + return iterator.isCompleted(); + } + + public static int countEmojis(String string) { + Trie.ContainmentIterator iterator = emojiTrie.iterator(); + int[] sequence = string.codePoints().toArray(); + + int count = 0; + for (int index = 0; index < sequence.length; index++) { + if (!iterator.doContinue(sequence[index])) { + if (iterator.isCompleted()) { + count++; + } + iterator.resetWith(sequence[index]); + } + } + if (iterator.isCompleted()) { + count++; + } + + return count; + } +} diff --git a/src/test/kotlin/sirius/kernel/commons/EmojiTest.kt b/src/test/kotlin/sirius/kernel/commons/EmojiTest.kt new file mode 100644 index 00000000..b1490307 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/EmojiTest.kt @@ -0,0 +1,77 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons + +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue + +/** + * Tests the [Trie] class. + */ +class EmojiTest { + + @Test + fun detectsSingleEmojisCorrectly() { + assertTrue { Emojis.isEmoji("👩🏾‍🚀") } + assertTrue { Emojis.isEmoji("🚒") } + assertTrue { Emojis.isEmoji("🌾") } + assertTrue { Emojis.isEmoji("😇") } + assertTrue { Emojis.isEmoji("🫛") } + assertTrue { Emojis.isEmoji("🐕‍🦺") } + assertTrue { Emojis.isEmoji("🇫🇷") } + assertTrue { Emojis.isEmoji("🥨") } + assertTrue { Emojis.isEmoji("🇿🇼") } + assertTrue { Emojis.isEmoji("🔣") } + assertTrue { Emojis.isEmoji("🎎") } + assertTrue { Emojis.isEmoji("🥺") } + + assertFalse { Emojis.isEmoji("a") } + assertFalse { Emojis.isEmoji("👍👍") } + } + + @Test + fun detectsPresenceOfEmojisCorrectly() { + assertTrue { Emojis.containsEmoji("Spaceflight! 👩🏾‍🚀") } + assertTrue { Emojis.containsEmoji("Yes 👍👍") } + assertTrue { Emojis.containsEmoji("😇") } + assertTrue { Emojis.containsEmoji("👍👍") } + + assertFalse { Emojis.containsEmoji("Nope") } + } + + @Test + fun detectsMultipleEmojisCorrectly() { + assertTrue { Emojis.onlyContainsEmojis("👩🏾‍🚀") } + assertTrue { Emojis.onlyContainsEmojis("👍👍") } + assertTrue { Emojis.onlyContainsEmojis("👩🏾‍🚀🪐🚀") } + + assertFalse { Emojis.onlyContainsEmojis("Nope") } + assertFalse { Emojis.onlyContainsEmojis("Nope 👎👎") } + assertFalse { Emojis.onlyContainsEmojis("👎👎 Nope") } + assertFalse { Emojis.onlyContainsEmojis(" 👎👎") } + assertFalse { Emojis.onlyContainsEmojis("👎 👎") } + assertFalse { Emojis.onlyContainsEmojis("👎👎 ") } + } + + @Test + fun countsEmojisCorrectly() { + assertEquals(0, Emojis.countEmojis("Hallo")) + assertEquals(1, Emojis.countEmojis("Hallo 🙂")) + assertEquals(1, Emojis.countEmojis("🥳")) + assertEquals(1, Emojis.countEmojis("🍾 Yay!")) + assertEquals(1, Emojis.countEmojis("Hallo 🙂 Wie geht's?")) + assertEquals(2, Emojis.countEmojis("Hallo 🙂👋")) + assertEquals(2, Emojis.countEmojis("😅🤦‍♂️")) + assertEquals(3, Emojis.countEmojis("👩🏾‍🚀🪐🚀")) + } +} From ce5c4d14ab2160dc08bd2c9b9c752ea337c7ca78 Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 1 Aug 2023 16:12:06 +0200 Subject: [PATCH 024/111] =?UTF-8?q?Uses=20`@snippet`=20tag=20for=20code=20?= =?UTF-8?q?sample=20=E2=9C=82=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/sirius/kernel/commons/BaseTrie.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/sirius/kernel/commons/BaseTrie.java b/src/main/java/sirius/kernel/commons/BaseTrie.java index 91e8dd0e..beb258a2 100644 --- a/src/main/java/sirius/kernel/commons/BaseTrie.java +++ b/src/main/java/sirius/kernel/commons/BaseTrie.java @@ -28,8 +28,7 @@ *

* An Example: If we have a list of stop words: "one", "two", "three" and want to detect if these occur in a * given text, we can do the following: - *

- * {@code
+ * {@snippet :
  * Trie trie = Trie.create();
  *
  * trie.put("one", true);
@@ -47,12 +46,11 @@
  *         iter.resetWith(check.charAt(i));
  *     }
  * }
+ *
  * if (iter.isCompleted()) {
  *     System.out.println("Found!");
  * }
- *
  * }
- * 
* * @param the type of values managed by the trie */ From 30e476a337b82aeb4341a5c9f73ac21d3239851d Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 1 Aug 2023 16:19:24 +0200 Subject: [PATCH 025/111] =?UTF-8?q?Adds=20missing=20documentation=20?= =?UTF-8?q?=F0=9F=93=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/sirius/kernel/commons/Emojis.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/sirius/kernel/commons/Emojis.java b/src/main/java/sirius/kernel/commons/Emojis.java index e6a1e87a..b41cff5c 100644 --- a/src/main/java/sirius/kernel/commons/Emojis.java +++ b/src/main/java/sirius/kernel/commons/Emojis.java @@ -4816,6 +4816,12 @@ public static boolean onlyContainsEmojis(String string) { return iterator.isCompleted(); } + /** + * Counts the emojis contained in the given {@code string}. + * + * @param string the string to check + * @return the number of emojis contained in the string + */ public static int countEmojis(String string) { Trie.ContainmentIterator iterator = emojiTrie.iterator(); int[] sequence = string.codePoints().toArray(); From 4aa5865f603f67d3bf3753fb15e01b53940fb2f3 Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 1 Aug 2023 16:20:03 +0200 Subject: [PATCH 026/111] =?UTF-8?q?Checks=20empty=20strings=20=F0=9F=AB=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/sirius/kernel/commons/Emojis.java | 12 ++++++++++++ src/test/kotlin/sirius/kernel/commons/EmojiTest.kt | 3 +++ 2 files changed, 15 insertions(+) diff --git a/src/main/java/sirius/kernel/commons/Emojis.java b/src/main/java/sirius/kernel/commons/Emojis.java index b41cff5c..ea6805ce 100644 --- a/src/main/java/sirius/kernel/commons/Emojis.java +++ b/src/main/java/sirius/kernel/commons/Emojis.java @@ -4764,6 +4764,10 @@ private Emojis() { * @see #onlyContainsEmojis(String) */ public static boolean isEmoji(String string) { + if (Strings.isEmpty(string)) { + return false; + } + return emojiTrie.get(string) != null; } @@ -4774,6 +4778,10 @@ public static boolean isEmoji(String string) { * @return true if the string contains at least one emoji, false otherwise */ public static boolean containsEmoji(String string) { + if (Strings.isEmpty(string)) { + return false; + } + Trie.ContainmentIterator iterator = emojiTrie.iterator(); int[] sequence = string.codePoints().toArray(); @@ -4799,6 +4807,10 @@ public static boolean containsEmoji(String string) { * @see #isEmoji(String) */ public static boolean onlyContainsEmojis(String string) { + if (Strings.isEmpty(string)) { + return false; + } + Trie.ContainmentIterator iterator = emojiTrie.iterator(); int[] sequence = string.codePoints().toArray(); diff --git a/src/test/kotlin/sirius/kernel/commons/EmojiTest.kt b/src/test/kotlin/sirius/kernel/commons/EmojiTest.kt index b1490307..f4d736d2 100644 --- a/src/test/kotlin/sirius/kernel/commons/EmojiTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/EmojiTest.kt @@ -35,6 +35,7 @@ class EmojiTest { assertTrue { Emojis.isEmoji("🎎") } assertTrue { Emojis.isEmoji("🥺") } + assertFalse { Emojis.isEmoji("") } assertFalse { Emojis.isEmoji("a") } assertFalse { Emojis.isEmoji("👍👍") } } @@ -46,6 +47,7 @@ class EmojiTest { assertTrue { Emojis.containsEmoji("😇") } assertTrue { Emojis.containsEmoji("👍👍") } + assertFalse { Emojis.containsEmoji("") } assertFalse { Emojis.containsEmoji("Nope") } } @@ -55,6 +57,7 @@ class EmojiTest { assertTrue { Emojis.onlyContainsEmojis("👍👍") } assertTrue { Emojis.onlyContainsEmojis("👩🏾‍🚀🪐🚀") } + assertFalse { Emojis.onlyContainsEmojis("") } assertFalse { Emojis.onlyContainsEmojis("Nope") } assertFalse { Emojis.onlyContainsEmojis("Nope 👎👎") } assertFalse { Emojis.onlyContainsEmojis("👎👎 Nope") } From 0900658f2069850d5a3e87555c360a367168fac4 Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 1 Aug 2023 16:24:05 +0200 Subject: [PATCH 027/111] =?UTF-8?q?Adds=20method=20for=20detecting=20emoji?= =?UTF-8?q?s=20with=20whitespace=20=F0=9F=90=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/sirius/kernel/commons/Emojis.java | 14 ++++++++++++++ .../kotlin/sirius/kernel/commons/EmojiTest.kt | 18 ++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/main/java/sirius/kernel/commons/Emojis.java b/src/main/java/sirius/kernel/commons/Emojis.java index ea6805ce..2841b331 100644 --- a/src/main/java/sirius/kernel/commons/Emojis.java +++ b/src/main/java/sirius/kernel/commons/Emojis.java @@ -4805,6 +4805,7 @@ public static boolean containsEmoji(String string) { * @param string the string to check * @return true if the string contains only emojis, false otherwise * @see #isEmoji(String) + * @see #onlyContainsEmojisWithWhitespace(String) */ public static boolean onlyContainsEmojis(String string) { if (Strings.isEmpty(string)) { @@ -4828,6 +4829,19 @@ public static boolean onlyContainsEmojis(String string) { return iterator.isCompleted(); } + /** + * Checks whether the given {@code string} contains only emojis with additional whitespace characters. + *

+ * Note that pure whitespace strings will return false. + * + * @param string the string to check + * @return true if the string contains only emojis and whitespace, false otherwise + * @see #onlyContainsEmojis(String) + */ + public static boolean onlyContainsEmojisWithWhitespace(String string) { + return onlyContainsEmojis(string.replaceAll("\\s", "")); + } + /** * Counts the emojis contained in the given {@code string}. * diff --git a/src/test/kotlin/sirius/kernel/commons/EmojiTest.kt b/src/test/kotlin/sirius/kernel/commons/EmojiTest.kt index f4d736d2..2e14522a 100644 --- a/src/test/kotlin/sirius/kernel/commons/EmojiTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/EmojiTest.kt @@ -8,11 +8,9 @@ package sirius.kernel.commons -import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import kotlin.test.assertEquals import kotlin.test.assertFalse -import kotlin.test.assertNull import kotlin.test.assertTrue /** @@ -66,6 +64,22 @@ class EmojiTest { assertFalse { Emojis.onlyContainsEmojis("👎👎 ") } } + @Test + fun detectsMultipleEmojisWithWhitespaceCorrectly() { + assertTrue { Emojis.onlyContainsEmojisWithWhitespace("👩🏾‍🚀") } + assertTrue { Emojis.onlyContainsEmojisWithWhitespace("👍👍") } + assertTrue { Emojis.onlyContainsEmojisWithWhitespace("👩🏾‍🚀🪐🚀") } + assertTrue { Emojis.onlyContainsEmojisWithWhitespace(" 👎👎") } + assertTrue { Emojis.onlyContainsEmojisWithWhitespace("👎 👎") } + assertTrue { Emojis.onlyContainsEmojisWithWhitespace("👎👎 ") } + + assertFalse { Emojis.onlyContainsEmojisWithWhitespace("") } + assertFalse { Emojis.onlyContainsEmojisWithWhitespace(" ") } + assertFalse { Emojis.onlyContainsEmojisWithWhitespace("Nope") } + assertFalse { Emojis.onlyContainsEmojisWithWhitespace("Nope 👎👎") } + assertFalse { Emojis.onlyContainsEmojisWithWhitespace("👎👎 Nope") } + } + @Test fun countsEmojisCorrectly() { assertEquals(0, Emojis.countEmojis("Hallo")) From 136b717077f3c9e8c1cb2eb8c1b2c127be1884db Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 1 Aug 2023 16:32:16 +0200 Subject: [PATCH 028/111] =?UTF-8?q?Adds=20methods=20for=20removing=20emoji?= =?UTF-8?q?s=20=F0=9F=97=91=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/sirius/kernel/commons/Emojis.java | 20 +++++++++++++++++++ .../kotlin/sirius/kernel/commons/EmojiTest.kt | 14 +++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/main/java/sirius/kernel/commons/Emojis.java b/src/main/java/sirius/kernel/commons/Emojis.java index 2841b331..72292392 100644 --- a/src/main/java/sirius/kernel/commons/Emojis.java +++ b/src/main/java/sirius/kernel/commons/Emojis.java @@ -4867,4 +4867,24 @@ public static int countEmojis(String string) { return count; } + + /** + * Removes all emojis from the given {@code string}. + * + * @param string the string to remove the emojis from + * @return the string without emojis + */ + public static String removeEmojis(String string) { + StringBuilder builder = new StringBuilder(); + Trie.ContainmentIterator iterator = emojiTrie.iterator(); + int[] sequence = string.codePoints().toArray(); + + for (int index = 0; index < sequence.length; index++) { + if (!iterator.doContinue(sequence[index]) && !iterator.resetWith(sequence[index])) { + builder.appendCodePoint(sequence[index]); + } + } + + return builder.toString(); + } } diff --git a/src/test/kotlin/sirius/kernel/commons/EmojiTest.kt b/src/test/kotlin/sirius/kernel/commons/EmojiTest.kt index 2e14522a..7e588527 100644 --- a/src/test/kotlin/sirius/kernel/commons/EmojiTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/EmojiTest.kt @@ -91,4 +91,18 @@ class EmojiTest { assertEquals(2, Emojis.countEmojis("😅🤦‍♂️")) assertEquals(3, Emojis.countEmojis("👩🏾‍🚀🪐🚀")) } + + @Test + fun removesEmojisCorrectly() { + assertEquals("", Emojis.removeEmojis("🥳")) + assertEquals("", Emojis.removeEmojis("😅🤦‍♂️")) + assertEquals("", Emojis.removeEmojis("👩🏾‍🚀🪐🚀")) + assertEquals("Hallo", Emojis.removeEmojis("Hallo")) + assertEquals("Hallo ", Emojis.removeEmojis("Hallo 🙂")) + assertEquals("Hallo ", Emojis.removeEmojis("Hallo 🙂👋")) + assertEquals(" Yay!", Emojis.removeEmojis("🍾 Yay!")) + assertEquals(" Yay!", Emojis.removeEmojis("🍾🥂 Yay!")) + assertEquals("Hallo Wie geht's?", Emojis.removeEmojis("Hallo 🙂 Wie geht's?")) + assertEquals("Hallo Wie geht's?", Emojis.removeEmojis("Hallo 🙂👋 Wie geht's?")) + } } From 1d0cd8a9cf2d27e5f98cec1c11ea4781cfefa7da Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Wed, 2 Aug 2023 11:14:10 +0200 Subject: [PATCH 029/111] =?UTF-8?q?Adds=20maintenance=20hint=20?= =?UTF-8?q?=F0=9F=92=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/sirius/kernel/commons/Emojis.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/sirius/kernel/commons/Emojis.java b/src/main/java/sirius/kernel/commons/Emojis.java index 72292392..1d7899fc 100644 --- a/src/main/java/sirius/kernel/commons/Emojis.java +++ b/src/main/java/sirius/kernel/commons/Emojis.java @@ -16,6 +16,10 @@ public class Emojis { protected static final CodePointTrie emojiTrie = CodePointTrie.create(); static { + // The following list is maintained by pasting the output of a Python generator script. It can be found at + // https://github.com/scireum/scireum-scripts/blob/main/sirius/list_emojis_for_insertion_into_kernel.py + // Emojis should be updated about once per year. + emojiTrie.put("😀", true); emojiTrie.put("😃", true); emojiTrie.put("😄", true); From 0c94de2d4a821e23387ac87abb8c7d178b2ad525 Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 7 Aug 2023 08:11:38 +0200 Subject: [PATCH 030/111] Moves test to kotlin package Fixes: OX-10305 --- .../sirius/kernel/timer/EveryDayTaskTest.groovy} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/{java/sirius/kernel/timer/EveryDayTaskSpec.groovy => kotlin/sirius/kernel/timer/EveryDayTaskTest.groovy} (97%) diff --git a/src/test/java/sirius/kernel/timer/EveryDayTaskSpec.groovy b/src/test/kotlin/sirius/kernel/timer/EveryDayTaskTest.groovy similarity index 97% rename from src/test/java/sirius/kernel/timer/EveryDayTaskSpec.groovy rename to src/test/kotlin/sirius/kernel/timer/EveryDayTaskTest.groovy index b063e0a0..4f88ac09 100644 --- a/src/test/java/sirius/kernel/timer/EveryDayTaskSpec.groovy +++ b/src/test/kotlin/sirius/kernel/timer/EveryDayTaskTest.groovy @@ -19,7 +19,7 @@ import java.time.Duration import java.time.Instant import java.time.ZoneId -class EveryDayTaskSpec extends BaseSpecification { +class EveryDayTaskTest extends BaseSpecification { @Part private static TimeProvider timeProvider From a2979053fb309df596e7c9da305b9aca80e32e57 Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 7 Aug 2023 08:16:59 +0200 Subject: [PATCH 031/111] Migrates every day task tests to jUnit5 Fixes: OX-10305 --- .../kernel/timer/EveryDayTaskTest.groovy | 61 ----------------- .../sirius/kernel/timer/EveryDayTaskTest.kt | 68 +++++++++++++++++++ 2 files changed, 68 insertions(+), 61 deletions(-) delete mode 100644 src/test/kotlin/sirius/kernel/timer/EveryDayTaskTest.groovy create mode 100644 src/test/kotlin/sirius/kernel/timer/EveryDayTaskTest.kt diff --git a/src/test/kotlin/sirius/kernel/timer/EveryDayTaskTest.groovy b/src/test/kotlin/sirius/kernel/timer/EveryDayTaskTest.groovy deleted file mode 100644 index 4f88ac09..00000000 --- a/src/test/kotlin/sirius/kernel/timer/EveryDayTaskTest.groovy +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.timer - -import sirius.kernel.BaseSpecification -import sirius.kernel.async.Future -import sirius.kernel.commons.TimeProvider -import sirius.kernel.commons.Wait -import sirius.kernel.di.std.Part - -import java.time.Clock -import java.time.Duration -import java.time.Instant -import java.time.ZoneId - -class EveryDayTaskTest extends BaseSpecification { - - @Part - private static TimeProvider timeProvider - - @Part - private static EndOfDayTaskExecutor endOfDayTaskExecutor - - @Part - private static Timers timers - - def "EveryDayTask is executed"() { - when: "We pretent that it is the start hour to execute all tasks..." - timeProvider.setClock(Clock.fixed(Instant.parse("2020-02-20T22:02:42.00Z"), ZoneId.systemDefault())) - and: - timers.runEveryDayTimers(22) - and: - EndOfDayTestTask.executed.await(Duration.ofSeconds(30)) - then: - EndOfDayTestTask.executed.isSuccessful() - cleanup: - timeProvider.setClock(Clock.systemDefaultZone()) - } - - def "EveryDayTask is not executed if timeout is reached"() { - setup: - EndOfDayTestTask.executed = new Future() - when: "We set an artificial clock in the morning where no tasks should run anymore..." - timeProvider.setClock(Clock.fixed(Instant.parse("2020-02-20T06:02:42.00Z"), ZoneId.systemDefault())) - and: "We pretend it to be the start hour of end of day tasks..." - timers.runEveryDayTimers(22) - and: - Wait.seconds(2) - then: - !EndOfDayTestTask.executed.isSuccessful() - cleanup: - timeProvider.setClock(Clock.systemDefaultZone()) - } - -} diff --git a/src/test/kotlin/sirius/kernel/timer/EveryDayTaskTest.kt b/src/test/kotlin/sirius/kernel/timer/EveryDayTaskTest.kt new file mode 100644 index 00000000..18813307 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/timer/EveryDayTaskTest.kt @@ -0,0 +1,68 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.timer + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import sirius.kernel.SiriusExtension +import sirius.kernel.async.Future +import sirius.kernel.commons.TimeProvider +import sirius.kernel.commons.Wait +import sirius.kernel.di.std.Part +import java.time.Clock +import java.time.Duration +import java.time.Instant +import java.time.ZoneId +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@ExtendWith(SiriusExtension::class) +class EveryDayTaskTest { + + @Test + fun `EveryDayTask is executed`() { + // We pretend that it is the start hour to execute all tasks... + timeProvider.setClock(Clock.fixed(Instant.parse("2020-02-20T22:02:42.00Z"), ZoneId.systemDefault())) + timers.runEveryDayTimers(22) + EndOfDayTestTask.executed.await(Duration.ofSeconds(30)) + + assertTrue { EndOfDayTestTask.executed.isSuccessful } + + // Reset the clock to the current time... + timeProvider.setClock(Clock.systemDefaultZone()) + } + + @Test + fun `EveryDayTask is not executed if timeout is reached`() { + EndOfDayTestTask.executed = Future() + // We set an artificial clock in the morning where no tasks should run anymore... + timeProvider.setClock(Clock.fixed(Instant.parse("2020-02-20T06:02:42.00Z"), ZoneId.systemDefault())) + // We pretend it to be the start hour of end of day tasks... + timers.runEveryDayTimers(22) + Wait.seconds(2.0) + assertFalse { EndOfDayTestTask.executed.isSuccessful } + + // Reset the clock to the current time... + timeProvider.setClock(Clock.systemDefaultZone()) + } + + companion object { + @Part + @JvmStatic + private lateinit var timeProvider: TimeProvider + + @Part + @JvmStatic + private lateinit var endOfDayTaskExecutor: EndOfDayTaskExecutor + + @Part + @JvmStatic + private lateinit var timers: Timers + } +} From 64dc4fabe34cd86378ba9f3a1a65cb9db670e2a3 Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 7 Aug 2023 08:17:57 +0200 Subject: [PATCH 032/111] Moves test to kotlin package Fixes: OX-10305 --- .../sirius/kernel/commons/AdvancedDateParserSpec.groovy | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/test/{java => kotlin}/sirius/kernel/commons/AdvancedDateParserSpec.groovy (100%) diff --git a/src/test/java/sirius/kernel/commons/AdvancedDateParserSpec.groovy b/src/test/kotlin/sirius/kernel/commons/AdvancedDateParserSpec.groovy similarity index 100% rename from src/test/java/sirius/kernel/commons/AdvancedDateParserSpec.groovy rename to src/test/kotlin/sirius/kernel/commons/AdvancedDateParserSpec.groovy From f65d3b5e3209e320a03c7f90842f842a91f9e7bc Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 7 Aug 2023 08:32:01 +0200 Subject: [PATCH 033/111] Migrates advanced date parser tests to jUnit5 Fixes: OX-10305 --- .../commons/AdvancedDateParserSpec.groovy | 147 ----------------- .../kernel/commons/AdvancedDateParserTest.kt | 153 ++++++++++++++++++ 2 files changed, 153 insertions(+), 147 deletions(-) delete mode 100644 src/test/kotlin/sirius/kernel/commons/AdvancedDateParserSpec.groovy create mode 100644 src/test/kotlin/sirius/kernel/commons/AdvancedDateParserTest.kt diff --git a/src/test/kotlin/sirius/kernel/commons/AdvancedDateParserSpec.groovy b/src/test/kotlin/sirius/kernel/commons/AdvancedDateParserSpec.groovy deleted file mode 100644 index 41160d87..00000000 --- a/src/test/kotlin/sirius/kernel/commons/AdvancedDateParserSpec.groovy +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.commons - -import sirius.kernel.BaseSpecification -import sirius.kernel.di.std.Part - -import java.text.ParseException -import java.time.Clock -import java.time.Instant -import java.time.LocalDateTime -import java.time.ZoneOffset - -class AdvancedDateParserSpec extends BaseSpecification { - - @Part - private static TimeProvider timeProvider - - def "German date can be parsed"() { - when: - AdvancedDateParser parser = new AdvancedDateParser("de") - timeProvider.setClock(Clock.fixed(Instant.parse("2017-07-07T12:34:55.00Z"), ZoneOffset.UTC)) - then: - parser.parse("07.07.2017").asDateTime() == LocalDateTime.parse("2017-07-07T00:00") - parser.parse("07.07.").asDateTime() == LocalDateTime.parse("2017-07-07T00:00") - parser.parse("07.07.17").asDateTime() == LocalDateTime.parse("2017-07-07T00:00") - parser.parse("heute").asDateTime() == LocalDateTime.parse("2017-07-07T12:34:55") - } - - def "An ISO date can be parsed"() { - when: - AdvancedDateParser parser = new AdvancedDateParser("de") - then: - parser.parse("2017-07-07T12:34:00").asDateTime() == LocalDateTime.parse("2017-07-07T12:34") - parser.parse("2017-07-07 12:34:00").asDateTime() == LocalDateTime.parse("2017-07-07T12:34") - parser.parse("2017-07-07").asDateTime() == LocalDateTime.parse("2017-07-07T00:00") - parser.parse("2017-07-07T00:00").asDateTime() == LocalDateTime.parse("2017-07-07T00:00") - parser.parse("2017-07-07T12:34:56").asDateTime() == LocalDateTime.parse("2017-07-07T12:34:56") - } - - def "American date format can be parsed"() { - when: - AdvancedDateParser parser = new AdvancedDateParser("en") - timeProvider.setClock(Clock.fixed(Instant.parse("2017-07-07T12:34:55.00Z"), ZoneOffset.UTC)) - then: - parser.parse("07/07/2017").asDateTime() == LocalDateTime.parse("2017-07-07T00:00") - parser.parse("07/07").asDateTime() == LocalDateTime.parse("2017-07-07T00:00") - parser.parse("07/07/17").asDateTime() == LocalDateTime.parse("2017-07-07T00:00") - parser.parse("today").asDateTime() == LocalDateTime.parse("2017-07-07T12:34:55") - parser.parse("07/07/17 11:00 am").asDateTime() == LocalDateTime.parse("2017-07-07T11:00:00") - parser.parse("07/27/17 11:00 am").asDateTime() == LocalDateTime.parse("2017-07-27T11:00:00") - parser.parse("today 11:00 pm").asDateTime() == LocalDateTime.parse("2017-07-07T23:00:00") - } - - def "British date format can be parsed"() { - when: - AdvancedDateParser parser = new AdvancedDateParser("en", true) - then: - parser.parse("27/07/17 11:00 am").asDateTime() == LocalDateTime.parse("2017-07-27T11:00:00") - when: - parser.parse("07/27/17 11:00 am").asDateTime() - then: - thrown(ParseException) - } - - def "French date formats can be parsed"() { - when: - AdvancedDateParser parser = new AdvancedDateParser("fr", true) - then: - parser.parse("27.07.17").asDateTime() == LocalDateTime.parse("2017-07-27T00:00:00") - parser.parse("27/07/17").asDateTime() == LocalDateTime.parse("2017-07-27T00:00:00") - when: - parser.parse("07/27/17").asDateTime() - then: - thrown(ParseException) - } - - def "Relative dates can be parsed"() { - when: - AdvancedDateParser parser = new AdvancedDateParser("de") - timeProvider.setClock(Clock.fixed(Instant.parse("2017-07-07T12:34:55.00Z"), ZoneOffset.UTC)) - then: - parser.parse("+1").asDateTime() == LocalDateTime.parse("2017-07-08T12:34:55") - parser.parse("+1 tag").asDateTime() == LocalDateTime.parse("2017-07-08T12:34:55") - parser.parse("heute - 1 woche").asDateTime() == LocalDateTime.parse("2017-06-30T12:34:55") - parser.parse("heute + 1 monat").asDateTime() == LocalDateTime.parse("2017-08-07T12:34:55") - } - - def "Ranges are properly enforced"() { - given: - AdvancedDateParser parser = new AdvancedDateParser("de") - when: "An out of range day of month is used..." - parser.parse("32.07.2017").asDateTime() - then: - thrown(ParseException) - when: "The number of days exceeds the days in a specific month..." - parser.parse("31.06.2017").asDateTime() - then: - thrown(ParseException) - when: "The month value exceeds 12..." - parser.parse("30.13.2017").asDateTime() - then: - thrown(ParseException) - when: "0 is used a day of month..." - parser.parse("00.12.2017").asDateTime() - then: - thrown(ParseException) - when: "An out of range value is used as hour of day..." - parser.parse("07.07.2017 25:00:00").asDateTime() - then: - thrown(ParseException) - when: "An out of range value is used as hour of day..." - parser.parse("07.07.2017 12pm").asDateTime() - then: - thrown(ParseException) - when: "An out of range value is used as hour of day..." - parser.parse("07.07.2017 12am").asDateTime() - then: - thrown(ParseException) - when: "An out of range value is used as minute of hour..." - parser.parse("07.07.2017 12:71:00").asDateTime() - then: - thrown(ParseException) - when: "An out of range value is used as second of minute..." - parser.parse("07.07.2017 12:00:71").asDateTime() - then: - thrown(ParseException) - } - - - def "The parser can re-digest its output"() { - given: - AdvancedDateParser parser = new AdvancedDateParser("de") - when: "A date is parsed into a DateSelection" - def dateSelection = parser.parse("07.07.2017 12:34:56") - then: "its result can be re-parsed by the date parser..." - parser.parse(dateSelection.toString()).asDateTime() == dateSelection.asDateTime() - } - - -} diff --git a/src/test/kotlin/sirius/kernel/commons/AdvancedDateParserTest.kt b/src/test/kotlin/sirius/kernel/commons/AdvancedDateParserTest.kt new file mode 100644 index 00000000..4ce4370a --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/AdvancedDateParserTest.kt @@ -0,0 +1,153 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.extension.ExtendWith +import sirius.kernel.SiriusExtension +import sirius.kernel.di.std.Part + +import java.text.ParseException +import java.time.Clock +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneOffset +import kotlin.test.assertEquals + +@ExtendWith(SiriusExtension::class) +class AdvancedDateParserTest { + + @Test + fun `German date can be parsed`() { + val parser = AdvancedDateParser("de") + timeProvider.setClock(Clock.fixed(Instant.parse("2017-07-07T12:34:55.00Z"), ZoneOffset.UTC)) + + assertEquals(LocalDateTime.parse("2017-07-07T00:00"), parser.parse("07.07.2017").asDateTime()) + assertEquals(LocalDateTime.parse("2017-07-07T00:00"), parser.parse("07.07.").asDateTime()) + assertEquals(LocalDateTime.parse("2017-07-07T00:00"), parser.parse("07.07.17").asDateTime()) + assertEquals(LocalDateTime.parse("2017-07-07T12:34:55"), parser.parse("heute").asDateTime()) + } + + @Test + fun `An ISO date can be parsed`() { + val parser = AdvancedDateParser("de") + + assertEquals(LocalDateTime.parse("2017-07-07T12:34"), parser.parse("2017-07-07T12:34:00").asDateTime()) + assertEquals(LocalDateTime.parse("2017-07-07T12:34"), parser.parse("2017-07-07 12:34:00").asDateTime()) + assertEquals(LocalDateTime.parse("2017-07-07T00:00"), parser.parse("2017-07-07").asDateTime()) + assertEquals(LocalDateTime.parse("2017-07-07T00:00"), parser.parse("2017-07-07T00:00").asDateTime()) + assertEquals(LocalDateTime.parse("2017-07-07T12:34:56"), parser.parse("2017-07-07T12:34:56").asDateTime()) + } + + @Test + fun `American date format can be parsed`() { + val parser = AdvancedDateParser("en") + timeProvider.setClock(Clock.fixed(Instant.parse("2017-07-07T12:34:55.00Z"), ZoneOffset.UTC)) + + assertEquals(LocalDateTime.parse("2017-07-07T00:00"), parser.parse("07/07/2017").asDateTime()) + assertEquals(LocalDateTime.parse("2017-07-07T00:00"), parser.parse("07/07").asDateTime()) + assertEquals(LocalDateTime.parse("2017-07-07T00:00"), parser.parse("07/07/17").asDateTime()) + assertEquals(LocalDateTime.parse("2017-07-07T12:34:55"), parser.parse("today").asDateTime()) + assertEquals(LocalDateTime.parse("2017-07-07T11:00:00"), parser.parse("07/07/17 11:00 am").asDateTime()) + assertEquals(LocalDateTime.parse("2017-07-27T11:00:00"), parser.parse("07/27/17 11:00 am").asDateTime()) + assertEquals(LocalDateTime.parse("2017-07-07T23:00:00"), parser.parse("today 11:00 pm").asDateTime()) + } + + @Test + fun `British date format can be parsed`() { + val parser = AdvancedDateParser("en", true) + + assertEquals(LocalDateTime.parse("2017-07-27T11:00:00"), parser.parse("27/07/17 11:00 am").asDateTime()) + + assertThrows { + parser.parse("07/27/17 11:00 am").asDateTime() + } + } + + @Test + fun `French date formats can be parsed`() { + val parser = AdvancedDateParser("fr", true) + + assertEquals(LocalDateTime.parse("2017-07-27T00:00:00"), parser.parse("27.07.17").asDateTime()) + assertEquals(LocalDateTime.parse("2017-07-27T00:00:00"), parser.parse("27/07/17").asDateTime()) + + assertThrows { + parser.parse("07/27/17").asDateTime() + } + } + + @Test + fun `Relative dates can be parsed`() { + val parser = AdvancedDateParser("de") + timeProvider.setClock(Clock.fixed(Instant.parse("2017-07-07T12:34:55.00Z"), ZoneOffset.UTC)) + + assertEquals(LocalDateTime.parse("2017-07-08T12:34:55"), parser.parse("+1").asDateTime()) + assertEquals(LocalDateTime.parse("2017-07-08T12:34:55"), parser.parse("+1 tag").asDateTime()) + assertEquals(LocalDateTime.parse("2017-06-30T12:34:55"), parser.parse("heute - 1 woche").asDateTime()) + assertEquals(LocalDateTime.parse("2017-08-07T12:34:55"), parser.parse("heute + 1 monat").asDateTime()) + } + + @Test + fun `Ranges are properly enforced`() { + val parser = AdvancedDateParser("de") + + // An out-of-range day of month is used... + assertThrows { + parser.parse("32.07.2017").asDateTime() + } + // The number of days exceeds the days in a specific month... + assertThrows { + parser.parse("31.06.2017").asDateTime() + } + // The month value exceeds 12... + assertThrows { + parser.parse("30.13.2017").asDateTime() + } + // 0 is used a day of month... + assertThrows { + parser.parse("00.12.2017").asDateTime() + } + // An out-of-range value is used as hour of day... + assertThrows { + parser.parse("07.07.2017 25:00:00").asDateTime() + } + // An out-of-range value is used as hour of day... + assertThrows { + parser.parse("07.07.2017 12pm").asDateTime() + } + // An out-of-range value is used as hour of day... + assertThrows { + parser.parse("07.07.2017 12am").asDateTime() + } + // An out-of-range value is used as minute of hour... + assertThrows { + parser.parse("07.07.2017 12:71:00").asDateTime() + } + // An out-of-range value is used as second of minute... + assertThrows { + parser.parse("07.07.2017 12:00:71").asDateTime() + } + } + + @Test + fun `The parser can re-digest its output`() { + val parser = AdvancedDateParser("de") + // A date is parsed into a DateSelection + val dateSelection = parser.parse("07.07.2017 12:34:56") + // Its result can be re-parsed by the date parser... + assertEquals(dateSelection.asDateTime(), parser.parse(dateSelection.toString()).asDateTime()) + } + + companion object { + @Part + @JvmStatic + private lateinit var timeProvider: TimeProvider + } +} From 24146068bb5a65c1eeb8c117ca9c79ba5badbcdd Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 7 Aug 2023 08:33:27 +0200 Subject: [PATCH 034/111] Moves test to kotlin package Fixes: OX-10305 --- .../sirius/kernel/commons/CSVReaderTest.groovy} | 2 +- .../sirius/kernel/commons/CSVWriterTest.groovy} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/test/{java/sirius/kernel/commons/CSVReaderSpec.groovy => kotlin/sirius/kernel/commons/CSVReaderTest.groovy} (99%) rename src/test/{java/sirius/kernel/commons/CSVWriterSpec.groovy => kotlin/sirius/kernel/commons/CSVWriterTest.groovy} (99%) diff --git a/src/test/java/sirius/kernel/commons/CSVReaderSpec.groovy b/src/test/kotlin/sirius/kernel/commons/CSVReaderTest.groovy similarity index 99% rename from src/test/java/sirius/kernel/commons/CSVReaderSpec.groovy rename to src/test/kotlin/sirius/kernel/commons/CSVReaderTest.groovy index 8b9b1e37..3dcc4daa 100644 --- a/src/test/java/sirius/kernel/commons/CSVReaderSpec.groovy +++ b/src/test/kotlin/sirius/kernel/commons/CSVReaderTest.groovy @@ -12,7 +12,7 @@ import sirius.kernel.BaseSpecification import java.util.function.Consumer -class CSVReaderSpec extends BaseSpecification { +class CSVReaderTest extends BaseSpecification { def "valid CSV data can be parsed"() { given: diff --git a/src/test/java/sirius/kernel/commons/CSVWriterSpec.groovy b/src/test/kotlin/sirius/kernel/commons/CSVWriterTest.groovy similarity index 99% rename from src/test/java/sirius/kernel/commons/CSVWriterSpec.groovy rename to src/test/kotlin/sirius/kernel/commons/CSVWriterTest.groovy index 26a9e8a1..83a43704 100644 --- a/src/test/java/sirius/kernel/commons/CSVWriterSpec.groovy +++ b/src/test/kotlin/sirius/kernel/commons/CSVWriterTest.groovy @@ -10,7 +10,7 @@ package sirius.kernel.commons import sirius.kernel.BaseSpecification -class CSVWriterSpec extends BaseSpecification { +class CSVWriterTest extends BaseSpecification { def "simple data is output as CSV"() { given: From a6486d2c363115cd49eea83148e5c4f615a01dcd Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 7 Aug 2023 09:29:04 +0200 Subject: [PATCH 035/111] Migrates csv tests to jUnit5 Fixes: OX-10305 --- .../kernel/commons/CSVReaderTest.groovy | 233 ------------------ .../sirius/kernel/commons/CSVReaderTest.kt | 171 +++++++++++++ .../kernel/commons/CSVWriterTest.groovy | 168 ------------- .../sirius/kernel/commons/CSVWriterTest.kt | 161 ++++++++++++ 4 files changed, 332 insertions(+), 401 deletions(-) delete mode 100644 src/test/kotlin/sirius/kernel/commons/CSVReaderTest.groovy create mode 100644 src/test/kotlin/sirius/kernel/commons/CSVReaderTest.kt delete mode 100644 src/test/kotlin/sirius/kernel/commons/CSVWriterTest.groovy create mode 100644 src/test/kotlin/sirius/kernel/commons/CSVWriterTest.kt diff --git a/src/test/kotlin/sirius/kernel/commons/CSVReaderTest.groovy b/src/test/kotlin/sirius/kernel/commons/CSVReaderTest.groovy deleted file mode 100644 index 3dcc4daa..00000000 --- a/src/test/kotlin/sirius/kernel/commons/CSVReaderTest.groovy +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.commons - -import sirius.kernel.BaseSpecification - -import java.util.function.Consumer - -class CSVReaderTest extends BaseSpecification { - - def "valid CSV data can be parsed"() { - given: - def data = "a;b;c\n1;2;3\r\n4;5;6" - when: - List output = [] - and: - new CSVReader(new StringReader(data)).execute(({ row -> output.add(row) } as Consumer)) - then: - output.size() == 3 - and: - output.get(0).at("A") == "a" - and: - output.get(0).at("B") == "b" - and: - output.get(0).at("C") == "c" - and: - output.get(1).at("A") == "1" - and: - output.get(1).at("B") == "2" - and: - output.get(1).at("C") == "3" - and: - output.get(2).at("A") == "4" - and: - output.get(2).at("B") == "5" - and: - output.get(2).at("C") == "6" - - } - - def "empty cells become an empty string"() { - given: - def data = "a;;c" - when: - List output = [] - and: - new CSVReader(new StringReader(data)).execute(({ row -> output.add(row) } as Consumer)) - then: - output.size() == 1 - and: - output.get(0).at("A") == "a" - and: - output.get(0).at("B") == "" - and: - output.get(0).at("C") == "c" - } - - def "quotation is detected and handled correctly"() { - given: - def data = '"a";"b;";1/4";d' - when: - List output = [] - and: - new CSVReader(new StringReader(data)).execute(({ row -> output.add(row) } as Consumer)) - then: - output.size() == 1 - and: - output.get(0).at("A") == "a" - and: - output.get(0).at("B") == "b;" - and: - output.get(0).at("C") == '1/4"' - and: - output.get(0).at("D") == "d" - } - - def "escaping works"() { - given: - def data = '\\"a;\\;;\\\\;x' - when: - List output = [] - and: - new CSVReader(new StringReader(data)).execute(({ row -> output.add(row) } as Consumer)) - then: - output.size() == 1 - and: - output.get(0).at("A") == '"a' - and: - output.get(0).at("B") == ";" - and: - output.get(0).at("C") == "\\" - and: - output.get(0).at("D") == "x" - } - - def "empty cells work with and without quotation"() { - given: - def data = 'a;;"";d' - when: - List output = [] - and: - new CSVReader(new StringReader(data)).execute(({ row -> output.add(row) } as Consumer)) - then: - output.size() == 1 - and: - output.get(0).at("A") == 'a' - and: - output.get(0).at("B") == "" - and: - output.get(0).at("C") == "" - and: - output.get(0).at("D") == "d" - } - - def "multiline values work"() { - given: - def data = '"a\nb";"c\r\nd";e\nf' - when: - List output = [] - and: - new CSVReader(new StringReader(data)).execute(({ row -> output.add(row) } as Consumer)) - then: - output.size() == 2 - and: - output.get(0).at("A") == 'a\nb' - and: - output.get(0).at("B") == "c\r\nd" - and: - output.get(0).at("C") == "e" - and: - output.get(1).at("A") == "f" - } - - def "ignoring whitespaces works"() { - given: - def data = ' a ; "b" ;\t"c"\t; " \td\t ";\te\t;f ' - when: - List output = [] - and: - new CSVReader(new StringReader(data)).execute(({ row -> output.add(row) } as Consumer)) - then: - output.size() == 1 - and: - output.get(0).at("A") == ' a ' - and: - output.get(0).at("B") == "b" - and: - output.get(0).at("C") == "c" - and: - output.get(0).at("D") == " \td\t " - and: - output.get(0).at("E") == "\te\t" - and: - output.get(0).at("F") == "f " - } - - def "modified settings work"() { - given: - def data = '!a! : !b! :&:c' - when: - List output = [] - and: - new CSVReader(new StringReader(data)) - .withSeparator(':' as char) - .withQuotation('!' as char) - .withEscape('&' as char) - .notIgnoringWhitespaces() - .execute(({ row -> output.add(row) } as Consumer)) - then: - output.size() == 1 - and: - output.get(0).at("A") == 'a' - and: - output.get(0).at("B") == " !b! " - and: - output.get(0).at("C") == ":c" - } - - def "quoted strings treat double quotes as escaped quote"() { - given: - def data = '"test""X""";"a";b""' - when: - List output = [] - and: - new CSVReader(new StringReader(data)) - .execute(({ row -> output.add(row) } as Consumer)) - then: - output.size() == 1 - and: - output.get(0).at("A") == 'test"X"' - and: - output.get(0).at("B") == "a" - and: - output.get(0).at("C") == 'b""' - } - - def "limit the number of line to read"() { - given: - def data = "a;b;c\n1;2;3\r\n4;5;6" - def completeData = "" - for (int i = 0; i < 100; i++) { - completeData += data + "\n" - } - when: - List output = [] - and: - new CSVReader(new StringReader(completeData)).withLimit(new Limit(0, 100)).execute(({ row -> output.add(row) } as Consumer)) - then: - output.size() == 100 - } - - def "skip and limit the number of line to read"() { - given: - def data = "a;b;c\n1;2;3\r\n4;5;6" - def completeData = "" - for (int i = 0; i < 100; i++) { - completeData += data + "\n" - } - when: - List output = [] - and: - new CSVReader(new StringReader(completeData)).withLimit(new Limit(250, 100)).execute(({ row -> output.add(row) } as Consumer)) - then: - output.size() == 50 - } - -} diff --git a/src/test/kotlin/sirius/kernel/commons/CSVReaderTest.kt b/src/test/kotlin/sirius/kernel/commons/CSVReaderTest.kt new file mode 100644 index 00000000..369ba154 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/CSVReaderTest.kt @@ -0,0 +1,171 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import sirius.kernel.SiriusExtension +import java.io.StringReader +import kotlin.test.assertEquals + +@ExtendWith(SiriusExtension::class) +class CSVReaderTest { + + @Test + fun `valid CSV data can be parsed`() { + val data = "a;b;c\n1;2;3\r\n4;5;6" + val output = mutableListOf() + CSVReader(StringReader(data)).execute { output.add(it) } + + assertEquals(3, output.size) + assertEquals("a", output[0].at("A").rawString) + assertEquals("b", output[0].at("B").rawString) + assertEquals("c", output[0].at("C").rawString) + assertEquals("1", output[1].at("A").rawString) + assertEquals("2", output[1].at("B").rawString) + assertEquals("3", output[1].at("C").rawString) + assertEquals("4", output[2].at("A").rawString) + assertEquals("5", output[2].at("B").rawString) + assertEquals("6", output[2].at("C").rawString) + + } + + @Test + fun `empty cells become an empty string`() { + val data = "a;;c" + val output = mutableListOf() + CSVReader(StringReader(data)).execute { output.add(it) } + + assertEquals(1, output.size) + assertEquals("a", output[0].at("A").rawString) + assertEquals("", output[0].at("B").rawString) + assertEquals("c", output[0].at("C").rawString) + } + + @Test + fun `quotation is detected and handled correctly`() { + val data = """ + "a";"b;";1/4";d + """.trimIndent() + val output = mutableListOf() + CSVReader(StringReader(data)).execute { output.add(it) } + + assertEquals(1, output.size) + assertEquals("a", output[0].at("A").rawString) + assertEquals("b;", output[0].at("B").rawString) + assertEquals("1/4\"", output[0].at("C").rawString) + assertEquals("d", output[0].at("D").rawString) + } + + @Test + fun `escaping works`() { + val data = """ + \"a;\;;\\;x + """.trimIndent() + val output = mutableListOf() + CSVReader(StringReader(data)).execute { output.add(it) } + + assertEquals(1, output.size) + assertEquals("\"a", output[0].at("A").rawString) + assertEquals(";", output[0].at("B").rawString) + assertEquals("\\", output[0].at("C").rawString) + assertEquals("x", output[0].at("D").rawString) + } + + @Test + fun `empty cells work with and without quotation`() { + val data = """ + a;;"";d + """.trimIndent() + val output = mutableListOf() + CSVReader(StringReader(data)).execute { output.add(it) } + + assertEquals(1, output.size) + assertEquals("a", output[0].at("A").rawString) + assertEquals("", output[0].at("B").rawString) + assertEquals("", output[0].at("C").rawString) + assertEquals("d", output[0].at("D").rawString) + } + + @Test + fun `multiline values work`() { + val data = "\"a\nb\";\"c\r\nd\";e\nf" + val output = mutableListOf() + CSVReader(StringReader(data)).execute { output.add(it) } + + assertEquals(2, output.size) + assertEquals("a\nb", output[0].at("A").rawString) + assertEquals("c\r\nd", output[0].at("B").rawString) + assertEquals("e", output[0].at("C").rawString) + assertEquals("f", output[1].at("A").rawString) + } + + @Test + fun `ignoring whitespaces works`() { + val data = " a ; \"b\" ;\t\"c\"\t; \" \td\t \";\te\t;f " + val output = mutableListOf() + CSVReader(StringReader(data)).execute { output.add(it) } + + assertEquals(1, output.size) + assertEquals(" a ", output[0].at("A").rawString) + assertEquals("b", output[0].at("B").rawString) + assertEquals("c", output[0].at("C").rawString) + assertEquals(" \td\t ", output[0].at("D").rawString) + assertEquals("\te\t", output[0].at("E").rawString) + assertEquals("f ", output[0].at("F").rawString) + } + + @Test + fun `modified settings work`() { + val data = "!a! : !b! :&:c" + val output = mutableListOf() + + CSVReader(StringReader(data)).withSeparator(':').withQuotation('!').withEscape('&').notIgnoringWhitespaces() + .execute { output.add(it) } + + assertEquals(1, output.size) + assertEquals("a", output[0].at("A").rawString) + assertEquals(" !b! ", output[0].at("B").rawString) + assertEquals(":c", output[0].at("C").rawString) + } + + @Test + fun `quoted strings treat double quotes as escaped quote`() { + val data = """ + "test""X""${'"'};"a";b"" + """.trimIndent() + val output = mutableListOf() + CSVReader(StringReader(data)).execute { output.add(it) } + + assertEquals(1, output.size) + assertEquals("test\"X\"", output[0].at("A").rawString) + assertEquals("a", output[0].at("B").rawString) + assertEquals("b\"\"", output[0].at("C").rawString) + } + + @Test + fun `limit the number of line to read`() { + val completeData = (0 .. 99).joinToString("\n") { "a;b;c\n1;2;3\r\n4;5;6" } + val output = mutableListOf() + CSVReader(StringReader(completeData)).withLimit(Limit(0, 100)).execute { output.add(it) } + + assertEquals(100, output.size) + } + + @Test + fun `skip and limit the number of line to read`() { + val completeData = (0..99).joinToString("\n") { "a;b;c\n1;2;3\r\n4;5;6" } + + val output = mutableListOf() + CSVReader(StringReader(completeData)).withLimit(Limit(250, 100)) + .execute { output.add(it) } + + assertEquals(50, output.size) + } +} diff --git a/src/test/kotlin/sirius/kernel/commons/CSVWriterTest.groovy b/src/test/kotlin/sirius/kernel/commons/CSVWriterTest.groovy deleted file mode 100644 index 83a43704..00000000 --- a/src/test/kotlin/sirius/kernel/commons/CSVWriterTest.groovy +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.commons - -import sirius.kernel.BaseSpecification - -class CSVWriterTest extends BaseSpecification { - - def "simple data is output as CSV"() { - given: - StringWriter output = new StringWriter() - and: - CSVWriter writer = new CSVWriter(output) - when: - writer.writeArray("a", "b", "c") - writer.writeArray(1, 2, 3) - writer.writeList(Arrays.asList("d", "e", "f")) - and: - output.close() - then: - output.toString() == "a;b;c\n1;2;3\nd;e;f" - } - - def "data with BOM can be written and re-read"() { - given: - StringWriter output = new StringWriter() - and: - CSVWriter writer = new CSVWriter(output) - when: - writer.writeUnicodeBOM() - writer.writeArray("a", "b", "c") - writer.writeArray(1, 2, 3) - writer.writeList(Arrays.asList("d", "e", "f")) - and: - output.close() - def csvData = output.toString() - def row = null - new CSVReader(new BOMReader(new StringReader(csvData))).execute({data -> if (row == null) { row = data } }) - then: - csvData.charAt(0) == Streams.UNICODE_BOM_CHARACTER - and: - row.at(0).asString() == "a" - } - - def "changing the lineSeparator works"() { - given: - StringWriter output = new StringWriter() - and: - CSVWriter writer = new CSVWriter(output).withLineSeparator("\r\n") - when: - writer.writeArray("a", "b", "c") - writer.writeArray(1, 2, 3) - writer.writeList(Arrays.asList("d", "e", "f")) - and: - output.close() - then: - output.toString() == "a;b;c\r\n1;2;3\r\nd;e;f" - } - - def "quotation works for separator and new line"() { - given: - StringWriter output = new StringWriter() - and: - CSVWriter writer = new CSVWriter(output) - when: - writer.writeArray("a;", "b", "c") - and: - writer.writeArray("1", "2\n2", "3") - and: - output.close() - then: - output.toString() == '"a;";b;c\n1;"2\n2";3' - } - - def "escaping of quotation works when using quotation"() { - given: - StringWriter output = new StringWriter() - and: - CSVWriter writer = new CSVWriter(output) - when: - writer.writeArray('"a"\nb') - writer.writeArray('"a";b') - then: - and: - output.close() - then: - output.toString() == '"\\"a\\"\nb"\n"\\"a\\";b"' - } - - def "escaping works for escape character and quotation"() { - given: - StringWriter output = new StringWriter() - and: - CSVWriter writer = new CSVWriter(output) - when: - writer.writeArray("a;b\"", "\\", "c") - and: - output.close() - then: - output.toString() == '"a;b\\"";\\\\;c' - } - - def "escaping of separator with escape-char works if there is no quotation-char"() { - given: - StringWriter output = new StringWriter() - and: - CSVWriter writer = new CSVWriter(output) - writer.withQuotation('\0' as char) - when: - writer.writeArray("a;b") - and: - output.close() - then: - output.toString() == 'a\\;b' - } - - def "throw an exception if we have to escape quotes, but there is no escape-char"() { - given: - StringWriter output = new StringWriter() - and: - CSVWriter writer = new CSVWriter(output) - writer.withEscape('\0' as char) - when: - writer.writeArray('"a";b') - and: - output.close() - then: - def e = thrown(IllegalArgumentException) - e.getMessage() == "Cannot output a quotation character within a quoted string without an escape character." - } - - def "throw an exception if there is a separator in the text, but there is no quotation-char and no escape-char"() { - given: - StringWriter output = new StringWriter() - and: - CSVWriter writer = new CSVWriter(output) - writer.withQuotation('\0' as char) - writer.withEscape('\0' as char) - when: - writer.writeArray('a;b') - and: - output.close() - then: - def e = thrown(IllegalArgumentException) - e.getMessage() == "Cannot output a column which contains the separator character ';' without an escape or quotation character." - } - - def "throw an exception if there is a new line in the text, but there is no quotation-char"() { - given: - StringWriter output = new StringWriter() - and: - CSVWriter writer = new CSVWriter(output) - writer.withQuotation('\0' as char) - when: - writer.writeArray('a\nb') - and: - output.close() - then: - def e = thrown(IllegalArgumentException) - e.getMessage() == "Cannot output a column which contains a line break without an quotation character." - } -} diff --git a/src/test/kotlin/sirius/kernel/commons/CSVWriterTest.kt b/src/test/kotlin/sirius/kernel/commons/CSVWriterTest.kt new file mode 100644 index 00000000..b9e9b11b --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/CSVWriterTest.kt @@ -0,0 +1,161 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.extension.ExtendWith +import sirius.kernel.SiriusExtension +import java.io.StringReader +import java.io.StringWriter +import kotlin.test.assertEquals + +@ExtendWith(SiriusExtension::class) +class CSVWriterTest { + + @Test + fun `simple data is output as CSV`() { + StringWriter().use { output -> + val writer = CSVWriter(output) + + writer.writeArray("a", "b", "c") + writer.writeArray(1, 2, 3) + writer.writeList(listOf("d", "e", "f")) + + assertEquals("a;b;c\n1;2;3\nd;e;f", output.toString()) + } + } + + @Test + fun `data with BOM can be written and re-read`() { + StringWriter().use { output -> + val writer = CSVWriter(output) + + writer.writeUnicodeBOM() + writer.writeArray("a", "b", "c") + writer.writeArray(1, 2, 3) + writer.writeList(listOf("d", "e", "f")) + + val csvData = output.toString() + var row: Values? = null + CSVReader(BOMReader(StringReader(csvData))).execute { data -> + if (row == null) { + row = data + } + } + + assertEquals(Streams.UNICODE_BOM_CHARACTER, csvData[0].code) + assertEquals("a", row?.at(0)?.asString()) + } + } + + @Test + fun `changing the lineSeparator works`() { + StringWriter().use { output -> + val writer = CSVWriter(output).withLineSeparator("\r\n") + + writer.writeArray("a", "b", "c") + writer.writeArray(1, 2, 3) + writer.writeList(listOf("d", "e", "f")) + + assertEquals("a;b;c\r\n1;2;3\r\nd;e;f", output.toString()) + } + } + + @Test + fun `quotation works for separator and new line`() { + StringWriter().use { output -> + val writer = CSVWriter(output) + + writer.writeArray("a;", "b", "c") + writer.writeArray("1", "2\n2", "3") + + assertEquals("\"a;\";b;c\n1;\"2\n2\";3", output.toString()) + } + } + + @Test + fun `escaping of quotation works when using quotation`() { + StringWriter().use { output -> + val writer = CSVWriter(output) + + writer.writeArray("\"a\"\nb") + writer.writeArray("\"a\";b") + + assertEquals("\"\\\"a\\\"\nb\"\n\"\\\"a\\\";b\"", output.toString()) + } + } + + @Test + fun `escaping works for escape character and quotation`() { + StringWriter().use { output -> + val writer = CSVWriter(output) + + writer.writeArray("a;b\"", "\\", "c") + + assertEquals("\"a;b\\\"\";\\\\;c", output.toString()) + } + } + + @Test + fun `escaping of separator with escape-char works if there is no quotation-char`() { + StringWriter().use { output -> + val writer = CSVWriter(output).withQuotation(0.toChar()) + + writer.writeArray("a;b") + + assertEquals("a\\;b", output.toString()) + } + } + + @Test + fun `throw an exception if we have to escape quotes, but there is no escape-char`() { + StringWriter().use { output -> + val writer = CSVWriter(output).withEscape(0.toChar()) + + val exception = assertThrows { + writer.writeArray("\"a\";b") + } + assertEquals( + "Cannot output a quotation character within a quoted string without an escape character.", + exception.message + ) + } + } + + @Test + fun `throw an exception if there is a separator in the text, but there is no quotation-char and no escape-char`() { + StringWriter().use { output -> + val writer = CSVWriter(output).withQuotation(0.toChar()).withEscape(0.toChar()) + + val exception = assertThrows { + writer.writeArray("'a;b") + } + assertEquals( + "Cannot output a column which contains the separator character ';' without an escape or quotation character.", + exception.message + ) + } + } + + @Test + fun `throw an exception if there is a new line in the text, but there is no quotation-char`() { + StringWriter().use { output -> + val writer = CSVWriter(output).withQuotation(0.toChar()) + + val exception = assertThrows { + writer.writeArray("a\nb") + } + assertEquals( + "Cannot output a column which contains a line break without an quotation character.", + exception.message + ) + } + } +} From 8a2462708659a1ddff8dc4843ac29d1cff95f801 Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 7 Aug 2023 09:52:11 +0200 Subject: [PATCH 036/111] Moves test to kotlin package Fixes: OX-10305 --- src/test/{java => kotlin}/sirius/kernel/commons/LimitTest.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/test/{java => kotlin}/sirius/kernel/commons/LimitTest.java (100%) diff --git a/src/test/java/sirius/kernel/commons/LimitTest.java b/src/test/kotlin/sirius/kernel/commons/LimitTest.java similarity index 100% rename from src/test/java/sirius/kernel/commons/LimitTest.java rename to src/test/kotlin/sirius/kernel/commons/LimitTest.java From d62d765fa06ac97e341d1361bbd6e193f17335f9 Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 7 Aug 2023 10:07:37 +0200 Subject: [PATCH 037/111] Migrates limit tests to jUnit5 Fixes: OX-10305 --- .../sirius/kernel/commons/LimitTest.java | 106 ------------------ .../kotlin/sirius/kernel/commons/LimitTest.kt | 96 ++++++++++++++++ 2 files changed, 96 insertions(+), 106 deletions(-) delete mode 100644 src/test/kotlin/sirius/kernel/commons/LimitTest.java create mode 100644 src/test/kotlin/sirius/kernel/commons/LimitTest.kt diff --git a/src/test/kotlin/sirius/kernel/commons/LimitTest.java b/src/test/kotlin/sirius/kernel/commons/LimitTest.java deleted file mode 100644 index 3dc63dbe..00000000 --- a/src/test/kotlin/sirius/kernel/commons/LimitTest.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.commons; - -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.*; - -class LimitTest { - - private void compare(String message, List given, int... expected) { - int[] givenArray = new int[given.size()]; - for (int i = 0; i < given.size(); i++) { - givenArray[i] = given.get(i); - } - assertArrayEquals(expected, givenArray, message); - } - - private void executeLimit(Limit limit, int expectedIterations, int... expected) { - List result = new ArrayList<>(); - int iterations = 0; - for (int i = 1; i <= 10; i++) { - iterations++; - if (limit.nextRow()) { - result.add(i); - } - if (!limit.shouldContinue()) { - break; - } - } - compare("Expected result not met for: " + limit, result, expected); - assertEquals(expectedIterations, iterations, "Expected iterations not met for: " + limit); - } - - @Test - void testSingleItem() { - executeLimit(Limit.singleItem(), 1, 1); - } - - @Test - void testUnlimited() { - executeLimit(Limit.UNLIMITED, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); - } - - @Test - void testSkip() { - executeLimit(new Limit(5, 0), 10, 6, 7, 8, 9, 10); - } - - @Test - void testLimit() { - executeLimit(new Limit(0, 5), 5, 1, 2, 3, 4, 5); - } - - @Test - void testSkipAndLimit() { - executeLimit(new Limit(2, 5), 7, 3, 4, 5, 6, 7); - } - - @Test - void testBadValues() { - // Negative start and negative limit are ignored - executeLimit(new Limit(-1, -5), 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); - } - - @Test - void testNullLimit() { - // Null as limit is unlimited - executeLimit(new Limit(0, null), 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); - } - - @Test - void testPredicate() { - // Use as predicate to filter a sublist of the given stream to compute a test sum - int sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).filter(new Limit(2, 4).asPredicate()).mapToInt(i -> i).sum(); - assertEquals(3 + 4 + 5 + 6, sum); - } - - @Test - void testRemaingItems() { - Limit l = new Limit(5, 500); - assertEquals(Integer.valueOf(505), l.getRemainingItems()); - l = new Limit(10, 0); - assertNull(l.getRemainingItems()); - l = new Limit(10, 10); - for (int i = 0; i < 12; i++) { - l.nextRow(); - } - assertEquals(Integer.valueOf(8), l.getRemainingItems()); - l = new Limit(10, 10); - for (int i = 0; i < 21; i++) { - l.nextRow(); - } - assertEquals(Integer.valueOf(0), l.getRemainingItems()); - } -} diff --git a/src/test/kotlin/sirius/kernel/commons/LimitTest.kt b/src/test/kotlin/sirius/kernel/commons/LimitTest.kt new file mode 100644 index 00000000..c04123b7 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/LimitTest.kt @@ -0,0 +1,96 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons + +import org.junit.jupiter.api.Test +import java.util.stream.Stream +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class LimitTest { + + @Test + fun testSingleItem() { + executeLimit(Limit.singleItem(), 1, listOf(1)) + } + + @Test + fun testUnlimited() { + executeLimit(Limit.UNLIMITED, 10, listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) + } + + @Test + fun testSkip() { + executeLimit(Limit(5, 0), 10, listOf(6, 7, 8, 9, 10)) + } + + @Test + fun testLimit() { + executeLimit(Limit(0, 5), 5, listOf(1, 2, 3, 4, 5)) + } + + @Test + fun testSkipAndLimit() { + executeLimit(Limit(2, 5), 7, listOf(3, 4, 5, 6, 7)) + } + + @Test + fun testBadValues() { + // Negative start and negative limit are ignored + executeLimit(Limit(-1, -5), 10, listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) + } + + @Test + fun testNullLimit() { + // Null as limit is unlimited + executeLimit(Limit(0, null), 10, listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) + } + + @Test + fun testPredicate() { + // Use as predicate to filter a sublist of the given stream to compute a test sum + val sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).filter(Limit(2, 4).asPredicate()).mapToInt { it }.sum() + assertEquals(3 + 4 + 5 + 6, sum) + } + + @Test + fun testRemainingItems() { + Limit(5, 500).apply { -> + assertEquals(505, this.remainingItems) + } + Limit(10, 0).apply { + assertNull(this.remainingItems) + } + Limit(10, 10).apply { + (0 until 12).forEach { _ -> this.nextRow() } + assertEquals(8, this.remainingItems) + } + Limit(10, 10).apply { + (0 until 21).forEach { _ -> this.nextRow() } + assertEquals(0, this.remainingItems) + } + } + + private fun executeLimit(limit: Limit, expectedIterations: Int, expected: List) { + val result = mutableListOf() + var iterations = 0 + for (i in 1..10) { + iterations++ + if (limit.nextRow()) { + result.add(i) + } + if (!limit.shouldContinue()) { + break + } + } + assertContentEquals(expected, result, "Expected result not met for: $limit") + assertEquals(expectedIterations, iterations, "Expected iterations not met for: $limit") + } +} From e1090d9bb32781df2e379d76a73a9d057d0dbc06 Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 7 Aug 2023 10:08:13 +0200 Subject: [PATCH 038/111] Moves test to kotlin package Fixes: OX-10305 --- .../sirius/kernel/commons/RomanNumeralTest.groovy} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename src/test/{java/sirius/kernel/commons/RomanNumeralSpec.groovy => kotlin/sirius/kernel/commons/RomanNumeralTest.groovy} (93%) diff --git a/src/test/java/sirius/kernel/commons/RomanNumeralSpec.groovy b/src/test/kotlin/sirius/kernel/commons/RomanNumeralTest.groovy similarity index 93% rename from src/test/java/sirius/kernel/commons/RomanNumeralSpec.groovy rename to src/test/kotlin/sirius/kernel/commons/RomanNumeralTest.groovy index 505fbe41..0ff2c50d 100644 --- a/src/test/java/sirius/kernel/commons/RomanNumeralSpec.groovy +++ b/src/test/kotlin/sirius/kernel/commons/RomanNumeralTest.groovy @@ -2,7 +2,7 @@ package sirius.kernel.commons import sirius.kernel.BaseSpecification -class RomanNumeralSpec extends BaseSpecification { +class RomanNumeralTest extends BaseSpecification { def "RomanNumeral converts int correctly to roman numeral strings"() { expect: @@ -33,4 +33,4 @@ class RomanNumeralSpec extends BaseSpecification { 1000 | "M" 2000 | "MM" } -} \ No newline at end of file +} From bdac1883da1a4b665ef2c12f6a4e3ab8fbf1b7d5 Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 7 Aug 2023 10:12:26 +0200 Subject: [PATCH 039/111] Migrates roman numerals tests to jUnit5 Fixes: OX-10305 --- .../kernel/commons/RomanNumeralTest.groovy | 36 ------------------ .../sirius/kernel/commons/RomanNumeralTest.kt | 38 +++++++++++++++++++ 2 files changed, 38 insertions(+), 36 deletions(-) delete mode 100644 src/test/kotlin/sirius/kernel/commons/RomanNumeralTest.groovy create mode 100644 src/test/kotlin/sirius/kernel/commons/RomanNumeralTest.kt diff --git a/src/test/kotlin/sirius/kernel/commons/RomanNumeralTest.groovy b/src/test/kotlin/sirius/kernel/commons/RomanNumeralTest.groovy deleted file mode 100644 index 0ff2c50d..00000000 --- a/src/test/kotlin/sirius/kernel/commons/RomanNumeralTest.groovy +++ /dev/null @@ -1,36 +0,0 @@ -package sirius.kernel.commons - -import sirius.kernel.BaseSpecification - -class RomanNumeralTest extends BaseSpecification { - - def "RomanNumeral converts int correctly to roman numeral strings"() { - expect: - RomanNumeral.toRoman(input) == output - where: - input | output - 0 | "" - 1 | "I" - 2 | "II" - 3 | "III" - 4 | "IV" - 5 | "V" - 6 | "VI" - 7 | "VII" - 8 | "VIII" - 9 | "IX" - 10 | "X" - 20 | "XX" - 40 | "XL" - 50 | "L" - 90 | "XC" - 100 | "C" - 200 | "CC" - 400 | "CD" - 500 | "D" - 800 | "DCCC" - 900 | "CM" - 1000 | "M" - 2000 | "MM" - } -} diff --git a/src/test/kotlin/sirius/kernel/commons/RomanNumeralTest.kt b/src/test/kotlin/sirius/kernel/commons/RomanNumeralTest.kt new file mode 100644 index 00000000..b4648f5d --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/RomanNumeralTest.kt @@ -0,0 +1,38 @@ +package sirius.kernel.commons + +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource +import kotlin.test.assertEquals + +class RomanNumeralTest { + + @ParameterizedTest + @CsvSource( + "0,''", + "1,I", + "2,II", + "3,III", + "4,IV", + "5,V", + "6,VI", + "7,VII", + "8,VIII", + "9,IX", + "10,X", + "20,XX", + "40,XL", + "50,L", + "90,XC", + "100,C", + "200,CC", + "400,CD", + "500,D", + "800,DCCC", + "900,CM", + "1000,M", + "2000,MM" + ) + fun `RomanNumeral converts int correctly to roman numeral strings`(input: Int, output: String) { + assertEquals(output, RomanNumeral.toRoman(input)) + } +} From 5a2968c23c8d12e52fce8b1227df0c0c616e94a3 Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 7 Aug 2023 10:15:16 +0200 Subject: [PATCH 040/111] Adds some documentation to the test classes Fixes: OX-10305 --- .../kotlin/sirius/kernel/commons/AdvancedDateParserTest.kt | 3 +++ src/test/kotlin/sirius/kernel/commons/CSVReaderTest.kt | 5 ++++- src/test/kotlin/sirius/kernel/commons/CSVWriterTest.kt | 3 +++ src/test/kotlin/sirius/kernel/commons/EmojiTest.kt | 2 +- src/test/kotlin/sirius/kernel/commons/LimitTest.kt | 3 +++ src/test/kotlin/sirius/kernel/commons/RomanNumeralTest.kt | 3 +++ src/test/kotlin/sirius/kernel/settings/ConfigBuilderTest.kt | 3 +++ src/test/kotlin/sirius/kernel/timer/EveryDayTaskTest.kt | 3 +++ 8 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/sirius/kernel/commons/AdvancedDateParserTest.kt b/src/test/kotlin/sirius/kernel/commons/AdvancedDateParserTest.kt index 4ce4370a..c92fe807 100644 --- a/src/test/kotlin/sirius/kernel/commons/AdvancedDateParserTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/AdvancedDateParserTest.kt @@ -21,6 +21,9 @@ import java.time.LocalDateTime import java.time.ZoneOffset import kotlin.test.assertEquals +/** + * Tests the [AdvancedDateParser] class. + */ @ExtendWith(SiriusExtension::class) class AdvancedDateParserTest { diff --git a/src/test/kotlin/sirius/kernel/commons/CSVReaderTest.kt b/src/test/kotlin/sirius/kernel/commons/CSVReaderTest.kt index 369ba154..e9ebaac0 100644 --- a/src/test/kotlin/sirius/kernel/commons/CSVReaderTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/CSVReaderTest.kt @@ -14,6 +14,9 @@ import sirius.kernel.SiriusExtension import java.io.StringReader import kotlin.test.assertEquals +/** + * Tests the [CSVReader] class. + */ @ExtendWith(SiriusExtension::class) class CSVReaderTest { @@ -151,7 +154,7 @@ class CSVReaderTest { @Test fun `limit the number of line to read`() { - val completeData = (0 .. 99).joinToString("\n") { "a;b;c\n1;2;3\r\n4;5;6" } + val completeData = (0..99).joinToString("\n") { "a;b;c\n1;2;3\r\n4;5;6" } val output = mutableListOf() CSVReader(StringReader(completeData)).withLimit(Limit(0, 100)).execute { output.add(it) } diff --git a/src/test/kotlin/sirius/kernel/commons/CSVWriterTest.kt b/src/test/kotlin/sirius/kernel/commons/CSVWriterTest.kt index b9e9b11b..e51b18b2 100644 --- a/src/test/kotlin/sirius/kernel/commons/CSVWriterTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/CSVWriterTest.kt @@ -16,6 +16,9 @@ import java.io.StringReader import java.io.StringWriter import kotlin.test.assertEquals +/** + * Tests the [CSVWriter] class. + */ @ExtendWith(SiriusExtension::class) class CSVWriterTest { diff --git a/src/test/kotlin/sirius/kernel/commons/EmojiTest.kt b/src/test/kotlin/sirius/kernel/commons/EmojiTest.kt index 7e588527..b5a484e0 100644 --- a/src/test/kotlin/sirius/kernel/commons/EmojiTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/EmojiTest.kt @@ -14,7 +14,7 @@ import kotlin.test.assertFalse import kotlin.test.assertTrue /** - * Tests the [Trie] class. + * Tests the [Emojis] class. */ class EmojiTest { diff --git a/src/test/kotlin/sirius/kernel/commons/LimitTest.kt b/src/test/kotlin/sirius/kernel/commons/LimitTest.kt index c04123b7..d73e81fe 100644 --- a/src/test/kotlin/sirius/kernel/commons/LimitTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/LimitTest.kt @@ -14,6 +14,9 @@ import kotlin.test.assertContentEquals import kotlin.test.assertEquals import kotlin.test.assertNull +/** + * Tests the [Limit] class. + */ class LimitTest { @Test diff --git a/src/test/kotlin/sirius/kernel/commons/RomanNumeralTest.kt b/src/test/kotlin/sirius/kernel/commons/RomanNumeralTest.kt index b4648f5d..bce02932 100644 --- a/src/test/kotlin/sirius/kernel/commons/RomanNumeralTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/RomanNumeralTest.kt @@ -4,6 +4,9 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.CsvSource import kotlin.test.assertEquals +/** + * Tests the [RomanNumeral] class. + */ class RomanNumeralTest { @ParameterizedTest diff --git a/src/test/kotlin/sirius/kernel/settings/ConfigBuilderTest.kt b/src/test/kotlin/sirius/kernel/settings/ConfigBuilderTest.kt index 94b02428..ae4c19da 100644 --- a/src/test/kotlin/sirius/kernel/settings/ConfigBuilderTest.kt +++ b/src/test/kotlin/sirius/kernel/settings/ConfigBuilderTest.kt @@ -11,6 +11,9 @@ package sirius.kernel.settings import kotlin.test.Test import kotlin.test.assertEquals +/** + * Tests the [ConfigBuilder] class. + */ class ConfigBuilderTest { @Test diff --git a/src/test/kotlin/sirius/kernel/timer/EveryDayTaskTest.kt b/src/test/kotlin/sirius/kernel/timer/EveryDayTaskTest.kt index 18813307..0fed2af4 100644 --- a/src/test/kotlin/sirius/kernel/timer/EveryDayTaskTest.kt +++ b/src/test/kotlin/sirius/kernel/timer/EveryDayTaskTest.kt @@ -22,6 +22,9 @@ import java.time.ZoneId import kotlin.test.assertFalse import kotlin.test.assertTrue +/** + * Tests the [EveryDay] class. + */ @ExtendWith(SiriusExtension::class) class EveryDayTaskTest { From 570a5461d2231af9364bce70b9d1ef6988cc6918 Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 7 Aug 2023 10:15:46 +0200 Subject: [PATCH 041/111] Removes test of a deprecated method Fixes: OX-10305 --- src/test/kotlin/sirius/kernel/async/PromiseTest.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/test/kotlin/sirius/kernel/async/PromiseTest.kt b/src/test/kotlin/sirius/kernel/async/PromiseTest.kt index ac50ff6f..c6ad4dcd 100644 --- a/src/test/kotlin/sirius/kernel/async/PromiseTest.kt +++ b/src/test/kotlin/sirius/kernel/async/PromiseTest.kt @@ -18,12 +18,6 @@ import kotlin.test.assertTrue */ class PromiseTest { - @Test - fun `A Promise can be converted to a Future`() { - val promise = Promise() - promise.asFuture() - } - @Test fun `Multiple Callbacks work on Promises when successfully completed`() { val test = Promise(); From 17c2f4df9db62cced321f18bc9d9671670750d9a Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Mon, 7 Aug 2023 10:17:22 +0200 Subject: [PATCH 042/111] Slightly improves code style of som tests Fixes: OX-10305 --- .../kotlin/sirius/kernel/async/PromiseTest.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/test/kotlin/sirius/kernel/async/PromiseTest.kt b/src/test/kotlin/sirius/kernel/async/PromiseTest.kt index c6ad4dcd..fa78e739 100644 --- a/src/test/kotlin/sirius/kernel/async/PromiseTest.kt +++ b/src/test/kotlin/sirius/kernel/async/PromiseTest.kt @@ -20,7 +20,7 @@ class PromiseTest { @Test fun `Multiple Callbacks work on Promises when successfully completed`() { - val test = Promise(); + val test = Promise() val resultHolder1 = ValueHolder.of("") val resultHolder2 = ValueHolder.of("") test.onSuccess(resultHolder1) @@ -32,8 +32,8 @@ class PromiseTest { // assert both handlers contain the expected result and the promise is considered successful and completed assertEquals(resultHolder1.get(), "Hello") assertEquals(resultHolder2.get(), "Hello") - test.isSuccessful() - test.isCompleted() + test.isSuccessful + test.isCompleted } @Test @@ -63,8 +63,8 @@ class PromiseTest { // both handlers contain the expected exception and the promise is considered 'failed' and 'completed' assertEquals(resultHolder1.get(), exception) assertEquals(resultHolder2.get(), exception) - assertTrue(test.isFailed()) - assertTrue(test.isCompleted()) + assertTrue { test.isFailed } + assertTrue { test.isCompleted } } @Test @@ -105,9 +105,9 @@ class PromiseTest { // complete the input with invalid data input.success(null) // the handler will contain the appropriate exception - assertTrue(resultHolder.get() is NullPointerException) + assertTrue { resultHolder.get() is NullPointerException } // the output promise is considered as 'failed' - assertTrue(output.isFailed()) + assertTrue { output.isFailed } } @Test @@ -121,7 +121,7 @@ class PromiseTest { // and mark it as failed as well test.fail(exception) // the promise is considered as 'failed' and is not considered as 'successful' - assertTrue(test.isFailed()) - assertTrue(!test.isSuccessful()) + assertTrue { test.isFailed } + assertTrue { !test.isSuccessful } } } From cdecc01b6070c517227da11f7c32bb90b0a82178 Mon Sep 17 00:00:00 2001 From: Matthias Keck Date: Fri, 11 Aug 2023 15:23:19 +0200 Subject: [PATCH 043/111] Extract magic string into constant Fixes: SIRI-860 --- src/main/java/sirius/kernel/xml/Outcall.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/sirius/kernel/xml/Outcall.java b/src/main/java/sirius/kernel/xml/Outcall.java index 76db87a8..d4180a82 100644 --- a/src/main/java/sirius/kernel/xml/Outcall.java +++ b/src/main/java/sirius/kernel/xml/Outcall.java @@ -119,6 +119,8 @@ public class Outcall { */ private static final int TIMEOUT_BLACKLIST_HIGH_WATERMARK = 100; + private static final String HEADER_AUTHORIZATION = "Authorization"; + @ConfigValue("http.outcall.timeouts.default.connectTimeout") private static Duration defaultConnectTimeout; @@ -271,7 +273,7 @@ public Outcall setAuthParams(String user, String password) throws IOException { String userAndPassword = user + ":" + password; String encodedAuthorization = Base64.getEncoder().encodeToString(userAndPassword.getBytes(charset)); - setRequestProperty("Authorization", "Basic " + encodedAuthorization); + setRequestProperty(HEADER_AUTHORIZATION, "Basic " + encodedAuthorization); return this; } From 1ed1ef323e1803b3933dbcd868030b39e1fb33b1 Mon Sep 17 00:00:00 2001 From: Matthias Keck Date: Fri, 11 Aug 2023 15:30:31 +0200 Subject: [PATCH 044/111] Introduces OAuth client support to outcall - new wither to set token supplier and refresh callback - if supplier is set, the authorization token is set Note: It needs to be formatted with the required type, this keeps the implementation here small and clean and specific requirements may get handled at the token supplier - refresh callback gets only used once, multiple refresh attempts make no sense - from outcall, existing JsonCall and XMLCall can use this functionality as well - weak spot is the logic in isUnauthorized. oauth2 does not set solid definitions on how to behave here. some api endpoints might even respond with status code 200 and an error message Fixes: SIRI-860 --- src/main/java/sirius/kernel/xml/Outcall.java | 40 ++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/main/java/sirius/kernel/xml/Outcall.java b/src/main/java/sirius/kernel/xml/Outcall.java index d4180a82..42d26742 100644 --- a/src/main/java/sirius/kernel/xml/Outcall.java +++ b/src/main/java/sirius/kernel/xml/Outcall.java @@ -62,6 +62,7 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -144,6 +145,8 @@ public class Outcall { private static String defaultUserAgent; private static final Average timeToFirstByte = new Average(); + private Supplier oAuthAccessToken; + private Runnable oAuthTokenRefresher; /** * Builds the default user agent string as 'product.name/product.version (+product.baseUrl)', where version or @@ -438,6 +441,10 @@ private void connect() throws IOException { return; } + if (oAuthAccessToken != null) { + setRequestProperty(HEADER_AUTHORIZATION, oAuthAccessToken.get()); + } + if (client == null) { client = clientBuilder.build(); } @@ -467,6 +474,14 @@ private void performRequest() throws IOException { .orElse(defaultConnectTimeout) .plusSeconds(1))) { response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); + if (oAuthTokenRefresher != null && isUnauthorized(response.statusCode())) { + oAuthTokenRefresher.run(); + oAuthTokenRefresher = null; + + requestBuilder.setHeader(HEADER_AUTHORIZATION, oAuthAccessToken.get()); + request = requestBuilder.build(); + performRequest(); + } } catch (InterruptedException exception) { Thread.currentThread().interrupt(); throw new IOException("Thread was interrupted!"); @@ -671,6 +686,22 @@ public static Average getTimeToFirstByte() { return timeToFirstByte; } + /** + * Enables OAuth token support for this outcall. + * + * @param accessTokenSupplier supplies the access token to be used for OAuth. It must contain the proper + * authorization type, e.g. 'Bearer ' + * @param tokenRefresher supplies the refreshed token to be used for OAuth. This means the supplier will + * perform the refresh of the token using OAuth refresh token flow. The token + * must contain the proper authorization type, e.g. 'Bearer <token>' + * @return the current instance for fluent method calls + */ + public Outcall withOAuth(Supplier accessTokenSupplier, Runnable tokenRefresher) { + this.oAuthAccessToken = accessTokenSupplier; + this.oAuthTokenRefresher = tokenRefresher; + return this; + } + private void installRedirectRequest(URI redirectedURI) { HttpRequest.Builder redirectBuilder = requestBuilder.copy(); redirectBuilder.uri(redirectedURI); @@ -700,6 +731,15 @@ private Optional checkForRedirectURI() throws IOException { return Optional.empty(); } + private boolean isUnauthorized(int statusCode) { + return switch (statusCode) { + case 400, 403 -> true; + // 400: Bad Request => authorization might be missing + // 403: Forbidden => authentication might be valid, but authorization is missing + default -> false; + }; + } + private boolean isRedirecting(int statusCode) { return switch (statusCode) { case 301, 302, 303, 307, 308 -> true; From 523ef2b1409fdcc0739b217469a35ccf64ea228a Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 28 Aug 2023 10:16:42 +0200 Subject: [PATCH 045/111] part one of ported NLS tests --- src/test/kotlin/sirius/kernel/nls/NLStest.kt | 119 +++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/test/kotlin/sirius/kernel/nls/NLStest.kt diff --git a/src/test/kotlin/sirius/kernel/nls/NLStest.kt b/src/test/kotlin/sirius/kernel/nls/NLStest.kt new file mode 100644 index 00000000..77fa1918 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/nls/NLStest.kt @@ -0,0 +1,119 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.nls + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource +import sirius.kernel.SiriusExtension +import sirius.kernel.async.CallContext +import sirius.kernel.commons.Amount +import java.time.* +import kotlin.test.assertEquals + +/** + * Tests the [NLS] class. + */ +@ExtendWith(SiriusExtension::class) + +class NLSTest { + + @Test + fun `toMachineString() formats a LocalDate as date without time`() { + val date = LocalDate.of(2014, 8, 9) + val result = NLS.toMachineString(date) + assertEquals("2014-08-09", result) + } + + @Test + fun `toMachineString() formats a LocalDateTime as date with time`() { + val date = LocalDateTime.of(2014, 8, 9, 12, 0, 59) + val result = NLS.toMachineString(date) + assertEquals("2014-08-09 12:00:59", result) + } + + @ParameterizedTest + @CsvSource( + delimiter = '|', textBlock = """ + 123456.789 | 123456.79 + 123456.81 | 123456.81 + 0.113 | 0.11 + -11111.1 | -11111.10 + 1 | 1.00 + -1 | -1.00 + 0 | 0.00""" + ) + fun `toMachineString() of Amount is properly formatted`( + input: Double, + output: String + ) { + assertEquals(NLS.toMachineString(Amount.of(input)), output) + } + + @Test + fun `toUserString() formats a LocalDateTime as date with time`() { + val date = LocalDateTime.of(2014, 8, 9, 12, 0, 59) + CallContext.getCurrent().language = "de" + val result = NLS.toUserString(date) + assertEquals("09.08.2014 12:00:59", result) + } + + @Test + fun `toUserString() formats a LocalTime as simple time`() { + val date = LocalTime.of(17, 23, 15) + CallContext.getCurrent().language = "de" + val result = NLS.toUserString(date) + assertEquals("17:23:15", result) + } + + + @Test + fun `toUserString() formats a LocalDate as date without time`() { + val date = LocalDate.of(2014, 8, 9) + CallContext.getCurrent().language = "de" + assertEquals("09.08.2014", NLS.toUserString(date)) + } + + @Test + fun `toUserString() formats an Instant successfully`() { + val instant = Instant.now() + val date = instant.atZone(ZoneId.systemDefault()).toLocalDateTime() + CallContext.getCurrent().language = "de" + val dateFormatted = NLS.toUserString(date) + val instantFormatted = NLS.toUserString(instant) + assertEquals(dateFormatted, instantFormatted) + } + + @Test + fun `toUserString() formats null as empty string`() { + val input = null + assertEquals("", NLS.toUserString(input)) + } + + @Test + fun `toSpokenDate() formats dates and dateTimes correctly`() { + assertEquals("heute", NLS.toSpokenDate(LocalDate.now())) + assertEquals("vor wenigen Minuten", NLS.toSpokenDate(LocalDateTime.now())) + assertEquals("gestern", NLS.toSpokenDate(LocalDate.now().minusDays(1))) + assertEquals("gestern", NLS.toSpokenDate(LocalDateTime.now().minusDays(1))) + assertEquals("morgen", NLS.toSpokenDate(LocalDate.now().plusDays(1))) + assertEquals("morgen", NLS.toSpokenDate(LocalDateTime.now().plusDays(1))) + assertEquals("01.01.2114", NLS.toSpokenDate(LocalDate.of(2114, 1, 1))) + assertEquals("01.01.2114", NLS.toSpokenDate(LocalDateTime.of(2114, 1, 1, 0, 0))) + assertEquals("01.01.2014", NLS.toSpokenDate(LocalDate.of(2014, 1, 1))) + assertEquals("01.01.2014", NLS.toSpokenDate(LocalDateTime.of(2014, 1, 1, 0, 0))) + assertEquals("vor wenigen Minuten", NLS.toSpokenDate(LocalDateTime.now().minusMinutes(5))) + assertEquals("vor 35 Minuten", NLS.toSpokenDate(LocalDateTime.now().minusMinutes(35))) + assertEquals("vor einer Stunde", NLS.toSpokenDate(LocalDateTime.now().minusHours(1))) + assertEquals("vor 4 Stunden", NLS.toSpokenDate(LocalDateTime.now().minusHours(4))) + assertEquals("in der nächsten Stunde", NLS.toSpokenDate(LocalDateTime.now().plusMinutes(40))) + assertEquals("in 3 Stunden", NLS.toSpokenDate(LocalDateTime.now().plusHours(4))) + } +} \ No newline at end of file From cd943c6b88e84deb8766c23cd9f04054e3291289 Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 28 Aug 2023 10:18:57 +0200 Subject: [PATCH 046/111] part three of ported NLS tests --- src/test/kotlin/sirius/kernel/nls/NLStest.kt | 44 ++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/test/kotlin/sirius/kernel/nls/NLStest.kt b/src/test/kotlin/sirius/kernel/nls/NLStest.kt index 77fa1918..d71553f0 100644 --- a/src/test/kotlin/sirius/kernel/nls/NLStest.kt +++ b/src/test/kotlin/sirius/kernel/nls/NLStest.kt @@ -116,4 +116,48 @@ class NLSTest { assertEquals("in der nächsten Stunde", NLS.toSpokenDate(LocalDateTime.now().plusMinutes(40))) assertEquals("in 3 Stunden", NLS.toSpokenDate(LocalDateTime.now().plusHours(4))) } + + @Test + fun `parseUserString with LocalTime parses 900 and 90023`() { + val input = "9:00" + val inputWithSeconds = "9:00:23" + assertEquals(9, NLS.parseUserString(LocalTime::class.java, input).getHour()) + assertEquals(9, NLS.parseUserString(LocalTime::class.java, inputWithSeconds).getHour()) + } + + @Test + fun `parseUserString for an Amount works`() { + val input = "34,54" + assertEquals("34,54", NLS.parseUserString(Amount::class.java, input).toString()) + } + + @ParameterizedTest + @CsvSource( + delimiter = '|', textBlock = """ + 42 | 42 + 77,0000 | 77""" + ) + fun `parseUserString works for integers`(input: String, output: Int) { + assertEquals(output, NLS.parseUserString(Int::class.java, input)) + } + + @ParameterizedTest + @CsvSource( + delimiter = '|', textBlock = """ + 12 | 12 + 31,0000 | 31""" + ) + fun `parseUserString works for longs`(input: String, output: Long) { + assertEquals(output, NLS.parseUserString(Long::class.java, input)) + } + + @ParameterizedTest + @CsvSource( + delimiter = '|', textBlock = """ + 55.000,00 | de | 55000 + 56,000.00 | en | 56000""" + ) + fun `parseUserString works for integers considering locale`(input: String, language: String, output: Int) { + assertEquals(output, NLS.parseUserString(Int::class.java, input, language)) + } } \ No newline at end of file From dfdff322c31c46d9e683364d6a008863020b4ffb Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 28 Aug 2023 10:25:01 +0200 Subject: [PATCH 047/111] part three of ported NLS tests --- src/test/kotlin/sirius/kernel/nls/NLStest.kt | 96 +++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/src/test/kotlin/sirius/kernel/nls/NLStest.kt b/src/test/kotlin/sirius/kernel/nls/NLStest.kt index d71553f0..26d0fd43 100644 --- a/src/test/kotlin/sirius/kernel/nls/NLStest.kt +++ b/src/test/kotlin/sirius/kernel/nls/NLStest.kt @@ -7,7 +7,10 @@ */ package sirius.kernel.nls - +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream +import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.params.ParameterizedTest @@ -17,6 +20,7 @@ import sirius.kernel.async.CallContext import sirius.kernel.commons.Amount import java.time.* import kotlin.test.assertEquals +import java.math.BigDecimal /** * Tests the [NLS] class. @@ -25,6 +29,45 @@ import kotlin.test.assertEquals class NLSTest { + companion object { + @JvmStatic + private fun `generator for parseUserString fails when expected`(): Stream? { + return Stream.of( + Arguments.of( + "42,1", + Int::class.java, + IllegalArgumentException::class.java, + "Bitte geben Sie eine gültige Zahl ein. '42,1' ist ungültig." + ), + Arguments.of( + "2999999999", + Int::class.java, IllegalArgumentException::class.java, + "Bitte geben Sie eine gültige Zahl ein. '2999999999' ist ungültig." + ), + Arguments.of( + "blub", + Double::class.java, IllegalArgumentException::class.java, + "Bitte geben Sie eine gültige Dezimalzahl ein. 'blub' ist ungültig." + ) + ) + } + + @JvmStatic + private fun `generator for parseUserString for a LocalTime works`(): Stream? { + return Stream.of( + Arguments.of( + "14:30:12", LocalTime.of(14, 30, 12, 0) + ), + Arguments.of( + "14:30", LocalTime.of(14, 30, 0, 0) + ), + Arguments.of( + "14", LocalTime.of(14, 0, 0, 0) + ) + ) + } + } + @Test fun `toMachineString() formats a LocalDate as date without time`() { val date = LocalDate.of(2014, 8, 9) @@ -160,4 +203,55 @@ class NLSTest { fun `parseUserString works for integers considering locale`(input: String, language: String, output: Int) { assertEquals(output, NLS.parseUserString(Int::class.java, input, language)) } + + @ParameterizedTest + @MethodSource("generator for parseUserString fails when expected") + fun `parseUserString fails when expected`( + input: String, + type: Class, + exception: Class, + message: String + ) { + val thrown: Exception = Assertions.assertThrows(exception) { + NLS.parseUserString(type, input) + } + Assertions.assertEquals(message, thrown.message) + } + + @ParameterizedTest + @MethodSource("generator for parseUserString for a LocalTime works") + fun `parseUserString for a LocalTime works`(input: String, output: LocalTime) { + assertEquals(output, NLS.parseUserString(LocalTime::class.java, input)) + } + + @Test + fun `parseMachineString works for decimals`() { + assertEquals(BigDecimal.ONE.divide(BigDecimal.TEN), NLS.parseMachineString(BigDecimal::class.java, "0.1")) + assertEquals(BigDecimal("0.1"), NLS.parseMachineString(BigDecimal::class.java, "0.1")) + } + + @Test + fun `parseMachineString works for integers`() { + assertEquals(23, NLS.parseMachineString(Int::class.java, "23")) + assertEquals(90, NLS.parseMachineString(Int::class.java, "90.0000")) + } + + @Test + fun `parseMachineString works for longs`() { + assertEquals(5L, NLS.parseMachineString(Long::class.java, "5")) + assertEquals(43L, NLS.parseMachineString(Long::class.java, "43.0000")) + } + + @Test + fun `getMonthNameShort correctly appends the given symbol`() { + Assertions.assertEquals("Jan.", NLS.getMonthNameShort(1, ".")) + Assertions.assertEquals("Mai", NLS.getMonthNameShort(5, ".")) + Assertions.assertEquals("März", NLS.getMonthNameShort(3, ".")) + Assertions.assertEquals("Juni", NLS.getMonthNameShort(6, ".")) + Assertions.assertEquals("Nov.", NLS.getMonthNameShort(11, ".")) + Assertions.assertEquals("Dez.", NLS.getMonthNameShort(12, ".")) + Assertions.assertEquals("", NLS.getMonthNameShort(0, ".")) + Assertions.assertEquals("", NLS.getMonthNameShort(13, ".")) + } + } \ No newline at end of file From 05a8bc786ef4764ec4f0196ff554a4000d4efaa7 Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 28 Aug 2023 11:18:19 +0200 Subject: [PATCH 048/111] added assertDoesNotThrow in 'formatters for null language don't throw exceptions and format using the current language' --- src/test/kotlin/sirius/kernel/nls/NLStest.kt | 214 ++++++++++++++++++- 1 file changed, 210 insertions(+), 4 deletions(-) diff --git a/src/test/kotlin/sirius/kernel/nls/NLStest.kt b/src/test/kotlin/sirius/kernel/nls/NLStest.kt index 26d0fd43..478a515e 100644 --- a/src/test/kotlin/sirius/kernel/nls/NLStest.kt +++ b/src/test/kotlin/sirius/kernel/nls/NLStest.kt @@ -7,20 +7,22 @@ */ package sirius.kernel.nls -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.MethodSource -import java.util.stream.Stream + import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.CsvSource +import org.junit.jupiter.params.provider.MethodSource import sirius.kernel.SiriusExtension import sirius.kernel.async.CallContext import sirius.kernel.commons.Amount +import java.math.BigDecimal import java.time.* +import java.util.stream.Stream import kotlin.test.assertEquals -import java.math.BigDecimal + /** * Tests the [NLS] class. @@ -242,6 +244,20 @@ class NLSTest { assertEquals(43L, NLS.parseMachineString(Long::class.java, "43.0000")) } + @ParameterizedTest + @MethodSource("generator for parseUserString fails when expected") + fun `parseMachineString fails when expected`( + input: String, + type: Class, + exception: Class, + message: String + ) { + val thrown: Exception = Assertions.assertThrows(exception) { + NLS.parseMachineString(type, input) + } + Assertions.assertEquals(message, thrown.message) + } + @Test fun `getMonthNameShort correctly appends the given symbol`() { Assertions.assertEquals("Jan.", NLS.getMonthNameShort(1, ".")) @@ -254,4 +270,194 @@ class NLSTest { Assertions.assertEquals("", NLS.getMonthNameShort(13, ".")) } + @ParameterizedTest + @CsvSource( + delimiter = '|', textBlock = """ + nls.test.withThree | 0 | zero + nls.test.withThree | 1 | one + nls.test.withThree | 2 | many: 2X + nls.test.withThree | -2 | many: -2X + nls.test.withTwo | 0 | many: 0X + nls.test.withTwo | 1 | one + nls.test.withTwo | 2 | many: 2X + nls.test.withTwo | -2 | many: -2X""" + ) + fun `get with numeric and autoformat works correctly`(property: String, numeric: Int, output: String) { + val function: (Int) -> String = { number -> number.toString() + 'X' } + Assertions.assertEquals(output, NLS.get(property, numeric, function, "en")) + } + + @Test + fun `unicode characters get imported without problems`() { + val loadedProperty = NLS.get("nls.test.utf8", "en") + Assertions.assertEquals( + "ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀƁƂƃƄƅƆƇƈƉƊƋƌƍƎƏƐƑƒƓƔƕƖƗƘƙƚƛƜƝƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀǁǂǃDŽDždžLJLjljNJNjnjǍǎǏǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴǵǺǻǼǽǾǿȀȁȂȃ...ЁЂЃЄЅІЇЈЉЊЋЌЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяёђѓєѕіїјљњћќўџѠѡѢѣѤѥѦѧѨѩѪѫѬѭѮѯѰѱѲѳѴѵѶѷѸѹѺѻѼѽѾѿҀҁ҂҃...ʹ͵ͺ;΄΅Ά·ΈΉΊΌΎΏΐΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩΪΫάέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώϐϑϒϓϔϕϖϚϜϞϠϢϣϤϥϦϧϨϩϪϫϬϭϮϯϰϱϲϳ،؛؟ءآأؤإئابةتثجحخدذرزسشصضطظعغـفقكلمنهوىيًٌٍَُِّْ٠١٢٣٤٥٦٧٨٩٪٫٬٭ٰٱٲٳٴٵٶٷٸٹٺٻټٽپٿڀځڂڃڄڅچڇڈډڊڋڌڍڎڏڐڑڒړڔڕږڗژڙښڛڜڝڞڟڠڡڢڣڤڥڦڧڨکڪګڬڭڮگڰڱ...¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♈♉♊♋♌♍♎♏♐♑♒♓♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯✁✂✃✄✆✇✈✉✌✍✎✏✐✑✒✓✔✕✖✗✘✙✚✛✜✝✞✟✠✡✢✣✤✥✦✧✩✪✫✬✭✮✯✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿❀❁❂❃❄❅❆❇❈❉❊❋❍❏❐❑❒❖❘❙❚❛❜❝❞❡❢❣❤❥❦❧❶❷❸❹❺❻❼❽❾❿➀➁➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓➔➘➙➚➛➜➝...", + loadedProperty + ) + } + + @ParameterizedTest + @CsvSource( + delimiter = '|', textBlock = + "nls.test.translate | nls.test.translate | de \n\$nls.test.translate | übersetzungs test | null\n\$nls.test.translate | übersetzungs test | de\n\$nls.test.translate | translation test | en" + ) + fun `test smartGet works correctly`(input: String, output: String, lang: String) { + Assertions.assertEquals(output, NLS.smartGet(input, lang)) + } + + @Test + fun `test various formatters`() { + val date = LocalDateTime.of(2000, 1, 2, 3, 4, 5) + Assertions.assertEquals("03:04", NLS.getTimeFormat("de").format(date)) + Assertions.assertEquals("03:04 AM", NLS.getTimeFormat("en").format(date)) + Assertions.assertEquals("02.01.2000 03:04:05", NLS.getDateTimeFormat("de").format(date)) + Assertions.assertEquals("01/02/2000 03:04:05", NLS.getDateTimeFormat("en").format(date)) + Assertions.assertEquals("02.01.2000 03:04", NLS.getDateTimeFormatWithoutSeconds("de").format(date)) + Assertions.assertEquals("01/02/2000 03:04", NLS.getDateTimeFormatWithoutSeconds("en").format(date)) + Assertions.assertEquals("03:04:05", NLS.getTimeFormatWithSeconds("de").format(date)) + Assertions.assertEquals("03:04:05 AM", NLS.getTimeFormatWithSeconds("en").format(date)) + } + + @Test + fun `formatters for null language don't throw exceptions and format using the current language`() { + val date = LocalDateTime.of(2000, 1, 2, 3, 4, 5) + val currentLang = NLS.getCurrentLanguage() + Assertions.assertEquals(NLS.getDateFormat(currentLang).format(date), NLS.getDateFormat(null).format(date)) + Assertions.assertDoesNotThrow { NLS.getDateFormat(null).format(date) } + Assertions.assertEquals( + NLS.getShortDateFormat(currentLang).format(date), + NLS.getShortDateFormat(null).format(date) + ) + Assertions.assertDoesNotThrow { NLS.getShortDateFormat(null).format(date) } + Assertions.assertEquals( + NLS.getTimeFormatWithSeconds(currentLang).format(date), + NLS.getTimeFormatWithSeconds(null).format(date) + ) + Assertions.assertDoesNotThrow { NLS.getTimeFormatWithSeconds(null).format(date) } + Assertions.assertEquals(NLS.getTimeFormat(currentLang).format(date), NLS.getTimeFormat(null).format(date)) + Assertions.assertDoesNotThrow { NLS.getTimeFormat(null).format(date) } + Assertions.assertEquals( + NLS.getTimeParseFormat(currentLang).format(date), + NLS.getTimeParseFormat(null).format(date) + ) + Assertions.assertDoesNotThrow { NLS.getTimeParseFormat(null).format(date) } + Assertions.assertEquals( + NLS.getDateTimeFormat(currentLang).format(date), + NLS.getDateTimeFormat(null).format(date) + ) + Assertions.assertDoesNotThrow { NLS.getDateTimeFormat(null).format(date) } + Assertions.assertEquals( + NLS + .getDateTimeFormatWithoutSeconds(currentLang).format(date), + NLS.getDateTimeFormatWithoutSeconds(null).format(date) + ) + Assertions.assertDoesNotThrow { NLS.getDateTimeFormatWithoutSeconds(null).format(date) } + } + + @Test + fun `convertDurationToDigitalClockFormat() of Duration is properly formatted`() { + Assertions.assertEquals("01:00:00", NLS.convertDurationToDigitalClockFormat(Duration.ofMinutes(60L))) + Assertions.assertEquals( + "01:00:01", + NLS.convertDurationToDigitalClockFormat(Duration.ofMinutes(60L).plusSeconds(1L)) + ) + Assertions.assertEquals( + "01:01:00", + NLS.convertDurationToDigitalClockFormat(Duration.ofHours(1).plusMinutes(1L)) + ) + Assertions.assertEquals( + "01:01:01", + NLS.convertDurationToDigitalClockFormat(Duration.ofHours(1).plusMinutes(1L).plusSeconds(1L)) + ) + Assertions.assertEquals("48:00:00", NLS.convertDurationToDigitalClockFormat(Duration.ofDays(2L))) + Assertions.assertEquals("49:00:00", NLS.convertDurationToDigitalClockFormat(Duration.ofDays(2L).plusHours(1L))) + Assertions.assertEquals( + "48:01:00", + NLS.convertDurationToDigitalClockFormat(Duration.ofDays(2L).plusMinutes(1L)) + ) + Assertions.assertEquals("00:00:01", NLS.convertDurationToDigitalClockFormat(Duration.ofSeconds(1L))) + Assertions.assertEquals("00:01:00", NLS.convertDurationToDigitalClockFormat(Duration.ofMinutes(1L))) + Assertions.assertEquals( + "00:01:01", + NLS.convertDurationToDigitalClockFormat(Duration.ofMinutes(1L).plusSeconds(1L)) + ) + } + + @Test + fun `convertDuration() of Duration is properly formatted`() { + assertEquals("1 Stunde", NLS.convertDuration(Duration.ofMinutes(60L), true, true)) + assertEquals("1 Stunde", NLS.convertDuration(Duration.ofMinutes(60L), true, false)) + assertEquals("1 Stunde", NLS.convertDuration(Duration.ofMinutes(60L), false, true)) + assertEquals("1 Stunde", NLS.convertDuration(Duration.ofMinutes(60L), false, false)) + assertEquals("2 Stunden", NLS.convertDuration(Duration.ofHours(2L), true, true)) + assertEquals("2 Stunden", NLS.convertDuration(Duration.ofHours(2L), true, false)) + assertEquals("2 Stunden", NLS.convertDuration(Duration.ofHours(2L), false, true)) + assertEquals("2 Stunden", NLS.convertDuration(Duration.ofHours(2L), false, false)) + assertEquals("1 Stunde, 1 Minute", NLS.convertDuration(Duration.ofMinutes(61L), true, true)) + assertEquals("1 Stunde, 1 Minute", NLS.convertDuration(Duration.ofMinutes(61L), true, false)) + assertEquals("1 Stunde, 1 Minute", NLS.convertDuration(Duration.ofMinutes(61L), false, true)) + assertEquals("1 Stunde, 1 Minute", NLS.convertDuration(Duration.ofMinutes(61L), false, false)) + assertEquals("1 Minute, 1 Sekunde", NLS.convertDuration(Duration.ofSeconds(61L), true, true)) + assertEquals("1 Minute, 1 Sekunde", NLS.convertDuration(Duration.ofSeconds(61L), true, false)) + assertEquals("1 Minute", NLS.convertDuration(Duration.ofSeconds(61L), false, true)) + assertEquals("1 Minute", NLS.convertDuration(Duration.ofSeconds(61L), false, false)) + assertEquals("2 Minuten", NLS.convertDuration(Duration.ofSeconds(121L), false, false)) + assertEquals("2 Minuten, 2 Sekunden", NLS.convertDuration(Duration.ofSeconds(122L), true, false)) + assertEquals("122 Tage", NLS.convertDuration(Duration.ofDays(122L), false, false)) + assertEquals("1 Tag", NLS.convertDuration(Duration.ofDays(1L), false, false)) + assertEquals("1 Tag, 30 Minuten", NLS.convertDuration(Duration.ofDays(1L).plusMinutes(30L), true, true)) + assertEquals( + "1 Tag, 7 Stunden, 30 Minuten", + NLS.convertDuration(Duration.ofDays(1L).plusHours(7).plusMinutes(30L), true, true) + ) + assertEquals( + "2 Tage, 30 Minuten", + NLS.convertDuration(Duration.ofDays(1L).plusHours(24).plusMinutes(30L), true, true) + ) + assertEquals( + "2 Tage, 2 Stunden, 30 Minuten", + NLS.convertDuration(Duration.ofDays(2L).plusHours(2).plusMinutes(30L), true, true) + ) + assertEquals( + "2 Tage, 2 Stunden, 30 Minuten, 22 Sekunden", + NLS.convertDuration(Duration.ofDays(2L).plusHours(2).plusMinutes(30L).plusSeconds(22L), true, true) + ) + assertEquals( + "2 Tage, 2 Stunden, 30 Minuten", + NLS.convertDuration(Duration.ofDays(2L).plusHours(2).plusMinutes(30L).plusSeconds(22L), false, true) + ) + assertEquals( + "2 Tage, 2 Stunden, 30 Minuten", + NLS.convertDuration( + Duration.ofDays(2L).plusHours(2).plusMinutes(30L).plusSeconds(22L).plusMillis(1L), + false, + true + ) + ) + assertEquals( + "2 Tage, 2 Stunden, 30 Minuten, 22 Sekunden, 1 Millisekunde", + NLS.convertDuration( + Duration.ofDays(2L).plusHours(2).plusMinutes(30L).plusSeconds(22L).plusMillis(1L), + true, + true + ) + ) + assertEquals( + "2 Tage, 2 Stunden, 30 Minuten, 1 Millisekunde", + NLS.convertDuration(Duration.ofDays(2L).plusHours(2).plusMinutes(30L).plusMillis(1L), true, true) + ) + assertEquals( + "2 Tage, 2 Stunden, 30 Minuten, 33 Millisekunden", + NLS.convertDuration(Duration.ofDays(2L).plusHours(2).plusMinutes(30L).plusMillis(33L), true, true) + ) + assertEquals("", NLS.convertDuration(Duration.ofDays(0L), true, true)) + assertEquals("", NLS.convertDuration(null, true, true)) + assertEquals("", NLS.convertDuration(Duration.ofMillis(101L), false, false)) + assertEquals("", NLS.convertDuration(Duration.ofMillis(101L), true, false)) + assertEquals("101 Millisekunden", NLS.convertDuration(Duration.ofMillis(101L), true, true)) + assertEquals("33 Sekunden", NLS.convertDuration(Duration.ofSeconds(33L), true, true)) + assertEquals("33 Sekunden", NLS.convertDuration(Duration.ofSeconds(33L), true, false)) + assertEquals("", NLS.convertDuration(Duration.ofSeconds(33L), false, false)) + } + } \ No newline at end of file From 71e8cdec22312fca26566baf7cb3d3937d3c10d2 Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 28 Aug 2023 11:19:01 +0200 Subject: [PATCH 049/111] added linebreak --- src/test/kotlin/sirius/kernel/nls/NLStest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/sirius/kernel/nls/NLStest.kt b/src/test/kotlin/sirius/kernel/nls/NLStest.kt index 478a515e..c204a42d 100644 --- a/src/test/kotlin/sirius/kernel/nls/NLStest.kt +++ b/src/test/kotlin/sirius/kernel/nls/NLStest.kt @@ -460,4 +460,4 @@ class NLSTest { assertEquals("", NLS.convertDuration(Duration.ofSeconds(33L), false, false)) } -} \ No newline at end of file +} From 10f11a3facb5e71896ec1d2d01c7f870a89dcdca Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 28 Aug 2023 11:33:27 +0200 Subject: [PATCH 050/111] Deleted groovy test --- .../java/sirius/kernel/nls/NLSSpec.groovy | 441 ------------------ 1 file changed, 441 deletions(-) delete mode 100644 src/test/java/sirius/kernel/nls/NLSSpec.groovy diff --git a/src/test/java/sirius/kernel/nls/NLSSpec.groovy b/src/test/java/sirius/kernel/nls/NLSSpec.groovy deleted file mode 100644 index c7959995..00000000 --- a/src/test/java/sirius/kernel/nls/NLSSpec.groovy +++ /dev/null @@ -1,441 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.nls - -import sirius.kernel.BaseSpecification -import sirius.kernel.async.CallContext -import sirius.kernel.commons.Amount - -import java.time.* - -class NLSSpec extends BaseSpecification { - - def "toMachineString() formats a LocalDate as date without time"() { - given: - def date = LocalDate.of(2014, 8, 9) - when: - def result = NLS.toMachineString(date) - then: - result == "2014-08-09" - } - - def "toMachineString() formats a LocalDateTime as date with time"() { - given: - def date = LocalDateTime.of(2014, 8, 9, 12, 00, 59) - when: - def result = NLS.toMachineString(date) - then: - result == "2014-08-09 12:00:59" - } - - def "toMachineString() of Amount is properly formatted"() { - expect: - NLS.toMachineString(Amount.of(input)) == output - where: - input | output - 123456.789 | "123456.79" - 123456.81 | "123456.81" - 0.113 | "0.11" - -11111.1 | "-11111.10" - 1 | "1.00" - -1 | "-1.00" - 0 | "0.00" - } - - def "toUserString() formats a LocalDateTime as date with time"() { - given: - def date = LocalDateTime.of(2014, 8, 9, 12, 00, 59) - and: - CallContext.getCurrent().setLanguage("de") - when: - def result = NLS.toUserString(date) - then: - result == "09.08.2014 12:00:59" - } - - def "toUserString() formats a LocalTime as simple time"() { - given: - def date = LocalTime.of(17, 23, 15) - and: - CallContext.getCurrent().setLanguage("de") - when: - def result = NLS.toUserString(date) - then: - result == "17:23:15" - } - - def "toUserString() formats a LocalDate as date without time"() { - given: - def date = LocalDate.of(2014, 8, 9) - and: - CallContext.getCurrent().setLanguage("de") - when: - def result = NLS.toUserString(date) - then: - result == "09.08.2014" - } - - def "toUserString() formats an Instant successfully"() { - given: - def instant = Instant.now() - def date = instant.atZone(ZoneId.systemDefault()).toLocalDateTime() - and: - CallContext.getCurrent().setLanguage("de") - when: - def dateFormatted = NLS.toUserString(date) - def instantFormatted = NLS.toUserString(instant) - then: - dateFormatted == instantFormatted - } - - def "toUserString() formats null as empty string"() { - given: - def input = null - when: - def result = NLS.toUserString(input) - then: - result == "" - } - - def "toSpokenDate() formats dates and dateTimes correctly"() { - expect: - NLS.toSpokenDate(date) == output - - where: - date | output - LocalDate.now() | "heute" - LocalDateTime.now() | "vor wenigen Minuten" - LocalDate.now().minusDays(1) | "gestern" - LocalDateTime.now().minusDays(1) | "gestern" - LocalDate.now().plusDays(1) | "morgen" - LocalDateTime.now().plusDays(1) | "morgen" - LocalDate.of(2114, 1, 1) | "01.01.2114" - LocalDateTime.of(2114, 1, 1, 0, 0) | "01.01.2114" - LocalDate.of(2014, 1, 1) | "01.01.2014" - LocalDateTime.of(2014, 1, 1, 0, 0) | "01.01.2014" - LocalDateTime.now().minusMinutes(5) | "vor wenigen Minuten" - LocalDateTime.now().minusMinutes(35) | "vor 35 Minuten" - LocalDateTime.now().minusHours(1) | "vor einer Stunde" - LocalDateTime.now().minusHours(4) | "vor 4 Stunden" - LocalDateTime.now().plusMinutes(40) | "in der nächsten Stunde" - LocalDateTime.now().plusHours(4) | "in 3 Stunden" // this correctly rounds down to 3 - - } - - def "parseUserString with LocalTime parses 9:00 and 9:00:23"() { - when: - def input = "9:00" - def inputWithSeconds = "9:00:23" - then: - NLS.parseUserString(LocalTime.class, input).getHour() == 9 - NLS.parseUserString(LocalTime.class, inputWithSeconds).getHour() == 9 - } - - def "parseUserString for an Amount works"() { - when: - def input = "34,54" - then: - NLS.parseUserString(Amount.class, input).toString() == "34,54" - } - - def "parseUserString works for integers"() { - expect: - NLS.parseUserString(Integer.class, input) == output - - where: - input | output - "42" | 42 - "77,0000" | 77 - } - - def "parseUserString works for longs"() { - expect: - NLS.parseUserString(Long.class, input) == output - - where: - input | output - "12" | 12L - "31,0000" | 31L - } - - def "parseUserString works for integers considering locale"() { - expect: - NLS.parseUserString(Integer.class, input, language) == output - - where: - input | language | output - "55.000,00" | "de" | 55000 - "56,000.00" | "en" | 56000 - } - - def "parseUserString fails when expected"() { - when: - NLS.parseUserString(clazz, input) == output - - then: - def error = thrown(expecteException) - error.message == expectedMessage - - where: - input | clazz | expecteException | expectedMessage - "42,1" | Integer - .class | IllegalArgumentException | "Bitte geben Sie eine gültige Zahl ein. '42,1' ist ungültig." - "2999999999" | Integer - .class | IllegalArgumentException | "Bitte geben Sie eine gültige Zahl ein. '2999999999' ist ungültig." - "blub" | Double - .class | IllegalArgumentException | "Bitte geben Sie eine gültige Dezimalzahl ein. 'blub' ist ungültig." - } - - def "parseUserString for a LocalTime works"() { - expect: - NLS.parseUserString(LocalTime.class, input) == output - - where: - input | output - "14:30:12" | LocalTime.of(14, 30, 12, 0) - "14:30" | LocalTime.of(14, 30, 0, 0) - "14" | LocalTime.of(14, 0, 0, 0) - } - - def "parseMachineString works for decimals"() { - expect: - NLS.parseMachineString(BigDecimal.class, input) == output - - where: - input | output - "0.1" | BigDecimal.ONE.divide(BigDecimal.TEN) - "0.1" | new BigDecimal("0.1") - } - - def "parseMachineString works for integers"() { - expect: - NLS.parseMachineString(Integer.class, input) == output - - where: - input | output - "23" | 23 - "90.0000" | 90 - } - - def "parseMachineString works for longs"() { - expect: - NLS.parseMachineString(Long.class, input) == output - - where: - input | output - "5" | 5L - "43.0000" | 43L - } - - def "parseMachineString fails when expected"() { - when: - NLS.parseMachineString(clazz, input) == output - - then: - def error = thrown(expecteException) - error.message == expectedMessage - - where: - input | clazz | expecteException | expectedMessage - "42.1" | Integer - .class | IllegalArgumentException | "Bitte geben Sie eine gültige Zahl ein. '42.1' ist ungültig." - "2999999999" | Integer - .class | IllegalArgumentException | "Bitte geben Sie eine gültige Zahl ein. '2999999999' ist ungültig." - "blub" | Double - .class | IllegalArgumentException | "Bitte geben Sie eine gültige Dezimalzahl ein. 'blub' ist ungültig." - } - - def "getMonthNameShort correctly appends the given symbol"() { - expect: - NLS.getMonthNameShort(month, ".") == output - - where: - month | output - 1 | "Jan." - 5 | "Mai" - 3 | "März" - 6 | "Juni" - 11 | "Nov." - 12 | "Dez." - 0 | "" - 13 | "" - } - - def "get with numeric works correctly"() { - expect: - NLS.get(property, numeric, "en") == output - - where: - property | numeric | output - "nls.test.withThree" | 0 | "zero" - "nls.test.withThree" | 1 | "one" - "nls.test.withThree" | 2 | "many: 2" - "nls.test.withThree" | -2 | "many: -2" - "nls.test.withTwo" | 0 | "many: 0" - "nls.test.withTwo" | 1 | "one" - "nls.test.withTwo" | 2 | "many: 2" - "nls.test.withTwo" | -2 | "many: -2" - } - - def "get with numeric and autoformat works correctly"() { - expect: - NLS.get(property, numeric, {number -> number + 'X'}, "en") == output - - where: - property | numeric | output - "nls.test.withThree" | 0 | "zero" - "nls.test.withThree" | 1 | "one" - "nls.test.withThree" | 2 | "many: 2X" - "nls.test.withThree" | -2 | "many: -2X" - "nls.test.withTwo" | 0 | "many: 0X" - "nls.test.withTwo" | 1 | "one" - "nls.test.withTwo" | 2 | "many: 2X" - "nls.test.withTwo" | -2 | "many: -2X" - } - - def "unicode characters get imported without problems"() { - given: - def loadedProperty = NLS.get("nls.test.utf8", "en") - expect: - loadedProperty == ("ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀƁƂƃƄƅƆƇƈƉƊƋƌƍƎƏƐƑƒƓƔƕƖƗƘƙƚƛƜƝƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀǁǂǃDŽDždžLJLjljNJNjnjǍǎǏǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴǵǺǻǼǽǾǿȀȁȂȃ...ЁЂЃЄЅІЇЈЉЊЋЌЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяёђѓєѕіїјљњћќўџѠѡѢѣѤѥѦѧѨѩѪѫѬѭѮѯѰѱѲѳѴѵѶѷѸѹѺѻѼѽѾѿҀҁ҂҃...ʹ͵ͺ;΄΅Ά·ΈΉΊΌΎΏΐΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩΪΫάέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώϐϑϒϓϔϕϖϚϜϞϠϢϣϤϥϦϧϨϩϪϫϬϭϮϯϰϱϲϳ،؛؟ءآأؤإئابةتثجحخدذرزسشصضطظعغـفقكلمنهوىيًٌٍَُِّْ٠١٢٣٤٥٦٧٨٩٪٫٬٭ٰٱٲٳٴٵٶٷٸٹٺٻټٽپٿڀځڂڃڄڅچڇڈډڊڋڌڍڎڏڐڑڒړڔڕږڗژڙښڛڜڝڞڟڠڡڢڣڤڥڦڧڨکڪګڬڭڮگڰڱ...¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♈♉♊♋♌♍♎♏♐♑♒♓♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯✁✂✃✄✆✇✈✉✌✍✎✏✐✑✒✓✔✕✖✗✘✙✚✛✜✝✞✟✠✡✢✣✤✥✦✧✩✪✫✬✭✮✯✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿❀❁❂❃❄❅❆❇❈❉❊❋❍❏❐❑❒❖❘❙❚❛❜❝❞❡❢❣❤❥❦❧❶❷❸❹❺❻❼❽❾❿➀➁➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓➔➘➙➚➛➜➝...") - } - - def "test smartGet works correctly"() { - expect: - NLS.smartGet(input, lang) == output - where: - input | output | lang - "nls.test.translate" | "nls.test.translate" | "de" - '$nls.test.translate' | "übersetzungs test" | null - '$nls.test.translate' | "übersetzungs test" | "de" - '$nls.test.translate' | "translation test" | "en" - } - - def "test various formatters"() { - given: - LocalDateTime date = LocalDateTime.of(2000, 1, 2, 3, 4, 5) - expect: - NLS.getTimeFormat("de").format(date) == "03:04" - NLS.getTimeFormat("en").format(date) == "03:04 AM" - NLS.getDateTimeFormat("de").format(date) == "02.01.2000 03:04:05" - NLS.getDateTimeFormat("en").format(date) == "01/02/2000 03:04:05" - NLS.getDateTimeFormatWithoutSeconds("de").format(date) == "02.01.2000 03:04" - NLS.getDateTimeFormatWithoutSeconds("en").format(date) == "01/02/2000 03:04" - NLS.getTimeFormatWithSeconds("de").format(date) == "03:04:05" - NLS.getTimeFormatWithSeconds("en").format(date) == "03:04:05 AM" - - } - - def "formatters for null language don't throw exceptions and format using the current language"() { - given: - LocalDateTime date = LocalDateTime.of(2000, 1, 2, 3, 4, 5) - when: - String currentLang = NLS.getCurrentLanguage() - then: - NLS.getDateFormat(null).format(date) == NLS.getDateFormat(currentLang).format(date) - NLS.getShortDateFormat(null).format(date) == NLS.getShortDateFormat(currentLang).format(date) - NLS.getTimeFormatWithSeconds(null).format(date) == NLS.getTimeFormatWithSeconds(currentLang).format(date) - NLS.getTimeFormat(null).format(date) == NLS.getTimeFormat(currentLang).format(date) - NLS.getTimeParseFormat(null).format(date) == NLS.getTimeParseFormat(currentLang).format(date) - NLS.getDateTimeFormat(null).format(date) == NLS.getDateTimeFormat(currentLang).format(date) - NLS.getDateTimeFormatWithoutSeconds(null).format(date) == NLS - .getDateTimeFormatWithoutSeconds(currentLang).format(date) - and: - noExceptionThrown() - } - - def "convertDurationToDigitalClockFormat() of Duration is properly formatted"() { - expect: - NLS.convertDurationToDigitalClockFormat(input) == output - where: - input | output - Duration.ofMinutes(60L) | "01:00:00" - Duration.ofMinutes(60L).plusSeconds(1L) | "01:00:01" - Duration.ofHours(1).plusMinutes(1L) | "01:01:00" - Duration.ofHours(1).plusMinutes(1L).plusSeconds(1L) | "01:01:01" - Duration.ofDays(2L) | "48:00:00" - Duration.ofDays(2L).plusHours(1L) | "49:00:00" - Duration.ofDays(2L).plusMinutes(1L) | "48:01:00" - Duration.ofSeconds(1L) | "00:00:01" - Duration.ofMinutes(1L) | "00:01:00" - Duration.ofMinutes(1L).plusSeconds(1L) | "00:01:01" - } - - def "convertDuration() of Duration is properly formatted"() { - expect: - NLS.convertDuration(duration, enableSeconds, enableMillis) == output - where: - duration | enableSeconds | enableMillis | output - Duration.ofMinutes(60L) | true | true | "1 Stunde" - Duration.ofMinutes(60L) | true | false | "1 Stunde" - Duration.ofMinutes(60L) | false | true | "1 Stunde" - Duration.ofMinutes(60L) | false | false | "1 Stunde" - Duration.ofHours(2L) | true | true | "2 Stunden" - Duration.ofHours(2L) | true | false | "2 Stunden" - Duration.ofHours(2L) | false | true | "2 Stunden" - Duration.ofHours(2L) | false | false | "2 Stunden" - Duration.ofMinutes(61L) | true | true | "1 Stunde, 1 Minute" - Duration.ofMinutes(61L) | true | false | "1 Stunde, 1 Minute" - Duration.ofMinutes(61L) | false | true | "1 Stunde, 1 Minute" - Duration.ofMinutes(61L) | false | false | "1 Stunde, 1 Minute" - Duration.ofSeconds(61L) | true | true | "1 Minute, 1 Sekunde" - Duration.ofSeconds(61L) | true | false | "1 Minute, 1 Sekunde" - Duration.ofSeconds(61L) | false | true | "1 Minute" - Duration.ofSeconds(61L) | false | false | "1 Minute" - Duration.ofSeconds(121L) | false | false | "2 Minuten" - Duration.ofSeconds(122L) | true | false | "2 Minuten, 2 Sekunden" - Duration.ofDays(122L) | false | false | "122 Tage" - Duration.ofDays(1L) | false | false | "1 Tag" - Duration.ofDays(1L) - .plusMinutes(30L) | true | true | "1 Tag, 30 Minuten" - Duration.ofDays(1L) - .plusHours(7) - .plusMinutes(30L) | true | true | "1 Tag, 7 Stunden, 30 Minuten" - Duration.ofDays(1L) - .plusHours(24) - .plusMinutes(30L) | true | true | "2 Tage, 30 Minuten" - Duration.ofDays(2L) - .plusHours(2) - .plusMinutes(30L) | true | true | "2 Tage, 2 Stunden, 30 Minuten" - Duration.ofDays(2L) - .plusHours(2) - .plusMinutes(30L) - .plusSeconds(22L) | true | true | "2 Tage, 2 Stunden, 30 Minuten, 22 Sekunden" - Duration.ofDays(2L) - .plusHours(2) - .plusMinutes(30L) - .plusSeconds(22L) | false | true | "2 Tage, 2 Stunden, 30 Minuten" - Duration.ofDays(2L) - .plusHours(2) - .plusMinutes(30L) - .plusSeconds(22L) - .plusMillis(1L) | false | true | "2 Tage, 2 Stunden, 30 Minuten" - Duration.ofDays(2L) - .plusHours(2) - .plusMinutes(30L) - .plusSeconds(22L) - .plusMillis(1L) | true | true | "2 Tage, 2 Stunden, 30 Minuten, 22 Sekunden, 1 Millisekunde" - Duration.ofDays(2L) - .plusHours(2) - .plusMinutes(30L) - .plusMillis(1L) | true | true | "2 Tage, 2 Stunden, 30 Minuten, 1 Millisekunde" - Duration.ofDays(2L) - .plusHours(2) - .plusMinutes(30L) - .plusMillis(33L) | true | true | "2 Tage, 2 Stunden, 30 Minuten, 33 Millisekunden" - Duration.ofDays(0L) | true | true | "" - null | true | true | "" - Duration.ofMillis(101L) | false | false | "" - Duration.ofMillis(101L) | true | false | "" - Duration.ofMillis(101L) | true | true | "101 Millisekunden" - Duration.ofSeconds(33L) | true | true | "33 Sekunden" - Duration.ofSeconds(33L) | true | false | "33 Sekunden" - Duration.ofSeconds(33L) | false | false | "" - } -} From f8bd7c81e9cf98f4501131b72b96c33d2aeee513 Mon Sep 17 00:00:00 2001 From: MOOOOOSER <92032959+MOOOOOSER@users.noreply.github.com> Date: Tue, 29 Aug 2023 10:53:35 +0200 Subject: [PATCH 051/111] Update src/test/kotlin/sirius/kernel/nls/NLStest.kt Co-authored-by: Idevaldo De Lira --- src/test/kotlin/sirius/kernel/nls/NLStest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/kotlin/sirius/kernel/nls/NLStest.kt b/src/test/kotlin/sirius/kernel/nls/NLStest.kt index c204a42d..3e187680 100644 --- a/src/test/kotlin/sirius/kernel/nls/NLStest.kt +++ b/src/test/kotlin/sirius/kernel/nls/NLStest.kt @@ -28,7 +28,6 @@ import kotlin.test.assertEquals * Tests the [NLS] class. */ @ExtendWith(SiriusExtension::class) - class NLSTest { companion object { From 6e386f43c140ecd4b9c55a80cb6c58c259617f8e Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Tue, 29 Aug 2023 14:23:19 +0200 Subject: [PATCH 052/111] fixes output messages when parsing a non decimal number Strings that cannot be parsed to integer or longs should throw an exception with NLS.errInvalidNumber key and not an exception with NLS.errInvalidDecimalNumber key. (this happened because of recursive call) --- src/main/java/sirius/kernel/nls/NLS.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/sirius/kernel/nls/NLS.java b/src/main/java/sirius/kernel/nls/NLS.java index 03c7bf5f..05519683 100644 --- a/src/main/java/sirius/kernel/nls/NLS.java +++ b/src/main/java/sirius/kernel/nls/NLS.java @@ -1059,7 +1059,7 @@ private static V parseBasicTypesFromMachineString(Class clazz, String val if (Integer.class.equals(clazz) || int.class.equals(clazz)) { try { return (V) Integer.valueOf(parseMachineString(BigDecimal.class, value).intValueExact()); - } catch (NumberFormatException | ArithmeticException exception) { + } catch (IllegalArgumentException | ArithmeticException exception) { throw new IllegalArgumentException(fmtr("NLS.errInvalidNumber").set("value", value).format(), exception); } @@ -1067,7 +1067,7 @@ private static V parseBasicTypesFromMachineString(Class clazz, String val if (Long.class.equals(clazz) || long.class.equals(clazz)) { try { return (V) Long.valueOf(parseMachineString(BigDecimal.class, value).longValueExact()); - } catch (NumberFormatException | ArithmeticException exception) { + } catch (IllegalArgumentException | ArithmeticException exception) { throw new IllegalArgumentException(fmtr("NLS.errInvalidNumber").set("value", value).format(), exception); } From 17f22c2ae7ae099ca835924be3d9c010dd30922b Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Tue, 29 Aug 2023 16:23:34 +0200 Subject: [PATCH 053/111] Added testcase for parseUserString and parseMachineString --- src/test/kotlin/sirius/kernel/nls/NLStest.kt | 189 ++++++++++--------- 1 file changed, 98 insertions(+), 91 deletions(-) diff --git a/src/test/kotlin/sirius/kernel/nls/NLStest.kt b/src/test/kotlin/sirius/kernel/nls/NLStest.kt index c204a42d..98a40576 100644 --- a/src/test/kotlin/sirius/kernel/nls/NLStest.kt +++ b/src/test/kotlin/sirius/kernel/nls/NLStest.kt @@ -35,37 +35,43 @@ class NLSTest { @JvmStatic private fun `generator for parseUserString fails when expected`(): Stream? { return Stream.of( - Arguments.of( - "42,1", - Int::class.java, - IllegalArgumentException::class.java, - "Bitte geben Sie eine gültige Zahl ein. '42,1' ist ungültig." - ), - Arguments.of( - "2999999999", - Int::class.java, IllegalArgumentException::class.java, - "Bitte geben Sie eine gültige Zahl ein. '2999999999' ist ungültig." - ), - Arguments.of( - "blub", - Double::class.java, IllegalArgumentException::class.java, - "Bitte geben Sie eine gültige Dezimalzahl ein. 'blub' ist ungültig." - ) + Arguments.of( + "42.1", + Integer::class.java, + IllegalArgumentException::class.java, + "Bitte geben Sie eine gültige Zahl ein. '42.1' ist ungültig." + ), + Arguments.of( + "42,1", + Integer::class.java, + IllegalArgumentException::class.java, + "Bitte geben Sie eine gültige Zahl ein. '42,1' ist ungültig." + ), + Arguments.of( + "2999999999", + Integer::class.java, IllegalArgumentException::class.java, + "Bitte geben Sie eine gültige Zahl ein. '2999999999' ist ungültig." + ), + Arguments.of( + "blub", + Double::class.java, IllegalArgumentException::class.java, + "Bitte geben Sie eine gültige Dezimalzahl ein. 'blub' ist ungültig." + ) ) } @JvmStatic private fun `generator for parseUserString for a LocalTime works`(): Stream? { return Stream.of( - Arguments.of( - "14:30:12", LocalTime.of(14, 30, 12, 0) - ), - Arguments.of( - "14:30", LocalTime.of(14, 30, 0, 0) - ), - Arguments.of( - "14", LocalTime.of(14, 0, 0, 0) - ) + Arguments.of( + "14:30:12", LocalTime.of(14, 30, 12, 0) + ), + Arguments.of( + "14:30", LocalTime.of(14, 30, 0, 0) + ), + Arguments.of( + "14", LocalTime.of(14, 0, 0, 0) + ) ) } } @@ -86,7 +92,7 @@ class NLSTest { @ParameterizedTest @CsvSource( - delimiter = '|', textBlock = """ + delimiter = '|', textBlock = """ 123456.789 | 123456.79 123456.81 | 123456.81 0.113 | 0.11 @@ -96,8 +102,8 @@ class NLSTest { 0 | 0.00""" ) fun `toMachineString() of Amount is properly formatted`( - input: Double, - output: String + input: Double, + output: String ) { assertEquals(NLS.toMachineString(Amount.of(input)), output) } @@ -178,7 +184,7 @@ class NLSTest { @ParameterizedTest @CsvSource( - delimiter = '|', textBlock = """ + delimiter = '|', textBlock = """ 42 | 42 77,0000 | 77""" ) @@ -188,7 +194,7 @@ class NLSTest { @ParameterizedTest @CsvSource( - delimiter = '|', textBlock = """ + delimiter = '|', textBlock = """ 12 | 12 31,0000 | 31""" ) @@ -198,7 +204,7 @@ class NLSTest { @ParameterizedTest @CsvSource( - delimiter = '|', textBlock = """ + delimiter = '|', textBlock = """ 55.000,00 | de | 55000 56,000.00 | en | 56000""" ) @@ -209,12 +215,13 @@ class NLSTest { @ParameterizedTest @MethodSource("generator for parseUserString fails when expected") fun `parseUserString fails when expected`( - input: String, - type: Class, - exception: Class, - message: String + input: String, + type: Class, + exception: Class, + message: String ) { val thrown: Exception = Assertions.assertThrows(exception) { + NLS.parseUserString(type, input) } Assertions.assertEquals(message, thrown.message) @@ -247,10 +254,10 @@ class NLSTest { @ParameterizedTest @MethodSource("generator for parseUserString fails when expected") fun `parseMachineString fails when expected`( - input: String, - type: Class, - exception: Class, - message: String + input: String, + type: Class, + exception: Class, + message: String ) { val thrown: Exception = Assertions.assertThrows(exception) { NLS.parseMachineString(type, input) @@ -272,7 +279,7 @@ class NLSTest { @ParameterizedTest @CsvSource( - delimiter = '|', textBlock = """ + delimiter = '|', textBlock = """ nls.test.withThree | 0 | zero nls.test.withThree | 1 | one nls.test.withThree | 2 | many: 2X @@ -291,15 +298,15 @@ class NLSTest { fun `unicode characters get imported without problems`() { val loadedProperty = NLS.get("nls.test.utf8", "en") Assertions.assertEquals( - "ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀƁƂƃƄƅƆƇƈƉƊƋƌƍƎƏƐƑƒƓƔƕƖƗƘƙƚƛƜƝƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀǁǂǃDŽDždžLJLjljNJNjnjǍǎǏǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴǵǺǻǼǽǾǿȀȁȂȃ...ЁЂЃЄЅІЇЈЉЊЋЌЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяёђѓєѕіїјљњћќўџѠѡѢѣѤѥѦѧѨѩѪѫѬѭѮѯѰѱѲѳѴѵѶѷѸѹѺѻѼѽѾѿҀҁ҂҃...ʹ͵ͺ;΄΅Ά·ΈΉΊΌΎΏΐΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩΪΫάέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώϐϑϒϓϔϕϖϚϜϞϠϢϣϤϥϦϧϨϩϪϫϬϭϮϯϰϱϲϳ،؛؟ءآأؤإئابةتثجحخدذرزسشصضطظعغـفقكلمنهوىيًٌٍَُِّْ٠١٢٣٤٥٦٧٨٩٪٫٬٭ٰٱٲٳٴٵٶٷٸٹٺٻټٽپٿڀځڂڃڄڅچڇڈډڊڋڌڍڎڏڐڑڒړڔڕږڗژڙښڛڜڝڞڟڠڡڢڣڤڥڦڧڨکڪګڬڭڮگڰڱ...¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♈♉♊♋♌♍♎♏♐♑♒♓♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯✁✂✃✄✆✇✈✉✌✍✎✏✐✑✒✓✔✕✖✗✘✙✚✛✜✝✞✟✠✡✢✣✤✥✦✧✩✪✫✬✭✮✯✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿❀❁❂❃❄❅❆❇❈❉❊❋❍❏❐❑❒❖❘❙❚❛❜❝❞❡❢❣❤❥❦❧❶❷❸❹❺❻❼❽❾❿➀➁➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓➔➘➙➚➛➜➝...", - loadedProperty + "ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀƁƂƃƄƅƆƇƈƉƊƋƌƍƎƏƐƑƒƓƔƕƖƗƘƙƚƛƜƝƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀǁǂǃDŽDždžLJLjljNJNjnjǍǎǏǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴǵǺǻǼǽǾǿȀȁȂȃ...ЁЂЃЄЅІЇЈЉЊЋЌЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюяёђѓєѕіїјљњћќўџѠѡѢѣѤѥѦѧѨѩѪѫѬѭѮѯѰѱѲѳѴѵѶѷѸѹѺѻѼѽѾѿҀҁ҂҃...ʹ͵ͺ;΄΅Ά·ΈΉΊΌΎΏΐΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩΪΫάέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώϐϑϒϓϔϕϖϚϜϞϠϢϣϤϥϦϧϨϩϪϫϬϭϮϯϰϱϲϳ،؛؟ءآأؤإئابةتثجحخدذرزسشصضطظعغـفقكلمنهوىيًٌٍَُِّْ٠١٢٣٤٥٦٧٨٩٪٫٬٭ٰٱٲٳٴٵٶٷٸٹٺٻټٽپٿڀځڂڃڄڅچڇڈډڊڋڌڍڎڏڐڑڒړڔڕږڗژڙښڛڜڝڞڟڠڡڢڣڤڥڦڧڨکڪګڬڭڮگڰڱ...¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♈♉♊♋♌♍♎♏♐♑♒♓♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯✁✂✃✄✆✇✈✉✌✍✎✏✐✑✒✓✔✕✖✗✘✙✚✛✜✝✞✟✠✡✢✣✤✥✦✧✩✪✫✬✭✮✯✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿❀❁❂❃❄❅❆❇❈❉❊❋❍❏❐❑❒❖❘❙❚❛❜❝❞❡❢❣❤❥❦❧❶❷❸❹❺❻❼❽❾❿➀➁➂➃➄➅➆➇➈➉➊➋➌➍➎➏➐➑➒➓➔➘➙➚➛➜➝...", + loadedProperty ) } @ParameterizedTest @CsvSource( - delimiter = '|', textBlock = - "nls.test.translate | nls.test.translate | de \n\$nls.test.translate | übersetzungs test | null\n\$nls.test.translate | übersetzungs test | de\n\$nls.test.translate | translation test | en" + delimiter = '|', textBlock = + "nls.test.translate | nls.test.translate | de \n\$nls.test.translate | übersetzungs test | null\n\$nls.test.translate | übersetzungs test | de\n\$nls.test.translate | translation test | en" ) fun `test smartGet works correctly`(input: String, output: String, lang: String) { Assertions.assertEquals(output, NLS.smartGet(input, lang)) @@ -325,31 +332,31 @@ class NLSTest { Assertions.assertEquals(NLS.getDateFormat(currentLang).format(date), NLS.getDateFormat(null).format(date)) Assertions.assertDoesNotThrow { NLS.getDateFormat(null).format(date) } Assertions.assertEquals( - NLS.getShortDateFormat(currentLang).format(date), - NLS.getShortDateFormat(null).format(date) + NLS.getShortDateFormat(currentLang).format(date), + NLS.getShortDateFormat(null).format(date) ) Assertions.assertDoesNotThrow { NLS.getShortDateFormat(null).format(date) } Assertions.assertEquals( - NLS.getTimeFormatWithSeconds(currentLang).format(date), - NLS.getTimeFormatWithSeconds(null).format(date) + NLS.getTimeFormatWithSeconds(currentLang).format(date), + NLS.getTimeFormatWithSeconds(null).format(date) ) Assertions.assertDoesNotThrow { NLS.getTimeFormatWithSeconds(null).format(date) } Assertions.assertEquals(NLS.getTimeFormat(currentLang).format(date), NLS.getTimeFormat(null).format(date)) Assertions.assertDoesNotThrow { NLS.getTimeFormat(null).format(date) } Assertions.assertEquals( - NLS.getTimeParseFormat(currentLang).format(date), - NLS.getTimeParseFormat(null).format(date) + NLS.getTimeParseFormat(currentLang).format(date), + NLS.getTimeParseFormat(null).format(date) ) Assertions.assertDoesNotThrow { NLS.getTimeParseFormat(null).format(date) } Assertions.assertEquals( - NLS.getDateTimeFormat(currentLang).format(date), - NLS.getDateTimeFormat(null).format(date) + NLS.getDateTimeFormat(currentLang).format(date), + NLS.getDateTimeFormat(null).format(date) ) Assertions.assertDoesNotThrow { NLS.getDateTimeFormat(null).format(date) } Assertions.assertEquals( - NLS - .getDateTimeFormatWithoutSeconds(currentLang).format(date), - NLS.getDateTimeFormatWithoutSeconds(null).format(date) + NLS + .getDateTimeFormatWithoutSeconds(currentLang).format(date), + NLS.getDateTimeFormatWithoutSeconds(null).format(date) ) Assertions.assertDoesNotThrow { NLS.getDateTimeFormatWithoutSeconds(null).format(date) } } @@ -358,28 +365,28 @@ class NLSTest { fun `convertDurationToDigitalClockFormat() of Duration is properly formatted`() { Assertions.assertEquals("01:00:00", NLS.convertDurationToDigitalClockFormat(Duration.ofMinutes(60L))) Assertions.assertEquals( - "01:00:01", - NLS.convertDurationToDigitalClockFormat(Duration.ofMinutes(60L).plusSeconds(1L)) + "01:00:01", + NLS.convertDurationToDigitalClockFormat(Duration.ofMinutes(60L).plusSeconds(1L)) ) Assertions.assertEquals( - "01:01:00", - NLS.convertDurationToDigitalClockFormat(Duration.ofHours(1).plusMinutes(1L)) + "01:01:00", + NLS.convertDurationToDigitalClockFormat(Duration.ofHours(1).plusMinutes(1L)) ) Assertions.assertEquals( - "01:01:01", - NLS.convertDurationToDigitalClockFormat(Duration.ofHours(1).plusMinutes(1L).plusSeconds(1L)) + "01:01:01", + NLS.convertDurationToDigitalClockFormat(Duration.ofHours(1).plusMinutes(1L).plusSeconds(1L)) ) Assertions.assertEquals("48:00:00", NLS.convertDurationToDigitalClockFormat(Duration.ofDays(2L))) Assertions.assertEquals("49:00:00", NLS.convertDurationToDigitalClockFormat(Duration.ofDays(2L).plusHours(1L))) Assertions.assertEquals( - "48:01:00", - NLS.convertDurationToDigitalClockFormat(Duration.ofDays(2L).plusMinutes(1L)) + "48:01:00", + NLS.convertDurationToDigitalClockFormat(Duration.ofDays(2L).plusMinutes(1L)) ) Assertions.assertEquals("00:00:01", NLS.convertDurationToDigitalClockFormat(Duration.ofSeconds(1L))) Assertions.assertEquals("00:01:00", NLS.convertDurationToDigitalClockFormat(Duration.ofMinutes(1L))) Assertions.assertEquals( - "00:01:01", - NLS.convertDurationToDigitalClockFormat(Duration.ofMinutes(1L).plusSeconds(1L)) + "00:01:01", + NLS.convertDurationToDigitalClockFormat(Duration.ofMinutes(1L).plusSeconds(1L)) ) } @@ -407,48 +414,48 @@ class NLSTest { assertEquals("1 Tag", NLS.convertDuration(Duration.ofDays(1L), false, false)) assertEquals("1 Tag, 30 Minuten", NLS.convertDuration(Duration.ofDays(1L).plusMinutes(30L), true, true)) assertEquals( - "1 Tag, 7 Stunden, 30 Minuten", - NLS.convertDuration(Duration.ofDays(1L).plusHours(7).plusMinutes(30L), true, true) + "1 Tag, 7 Stunden, 30 Minuten", + NLS.convertDuration(Duration.ofDays(1L).plusHours(7).plusMinutes(30L), true, true) ) assertEquals( - "2 Tage, 30 Minuten", - NLS.convertDuration(Duration.ofDays(1L).plusHours(24).plusMinutes(30L), true, true) + "2 Tage, 30 Minuten", + NLS.convertDuration(Duration.ofDays(1L).plusHours(24).plusMinutes(30L), true, true) ) assertEquals( - "2 Tage, 2 Stunden, 30 Minuten", - NLS.convertDuration(Duration.ofDays(2L).plusHours(2).plusMinutes(30L), true, true) + "2 Tage, 2 Stunden, 30 Minuten", + NLS.convertDuration(Duration.ofDays(2L).plusHours(2).plusMinutes(30L), true, true) ) assertEquals( - "2 Tage, 2 Stunden, 30 Minuten, 22 Sekunden", - NLS.convertDuration(Duration.ofDays(2L).plusHours(2).plusMinutes(30L).plusSeconds(22L), true, true) + "2 Tage, 2 Stunden, 30 Minuten, 22 Sekunden", + NLS.convertDuration(Duration.ofDays(2L).plusHours(2).plusMinutes(30L).plusSeconds(22L), true, true) ) assertEquals( - "2 Tage, 2 Stunden, 30 Minuten", - NLS.convertDuration(Duration.ofDays(2L).plusHours(2).plusMinutes(30L).plusSeconds(22L), false, true) + "2 Tage, 2 Stunden, 30 Minuten", + NLS.convertDuration(Duration.ofDays(2L).plusHours(2).plusMinutes(30L).plusSeconds(22L), false, true) ) assertEquals( - "2 Tage, 2 Stunden, 30 Minuten", - NLS.convertDuration( - Duration.ofDays(2L).plusHours(2).plusMinutes(30L).plusSeconds(22L).plusMillis(1L), - false, - true - ) + "2 Tage, 2 Stunden, 30 Minuten", + NLS.convertDuration( + Duration.ofDays(2L).plusHours(2).plusMinutes(30L).plusSeconds(22L).plusMillis(1L), + false, + true + ) ) assertEquals( - "2 Tage, 2 Stunden, 30 Minuten, 22 Sekunden, 1 Millisekunde", - NLS.convertDuration( - Duration.ofDays(2L).plusHours(2).plusMinutes(30L).plusSeconds(22L).plusMillis(1L), - true, - true - ) + "2 Tage, 2 Stunden, 30 Minuten, 22 Sekunden, 1 Millisekunde", + NLS.convertDuration( + Duration.ofDays(2L).plusHours(2).plusMinutes(30L).plusSeconds(22L).plusMillis(1L), + true, + true + ) ) assertEquals( - "2 Tage, 2 Stunden, 30 Minuten, 1 Millisekunde", - NLS.convertDuration(Duration.ofDays(2L).plusHours(2).plusMinutes(30L).plusMillis(1L), true, true) + "2 Tage, 2 Stunden, 30 Minuten, 1 Millisekunde", + NLS.convertDuration(Duration.ofDays(2L).plusHours(2).plusMinutes(30L).plusMillis(1L), true, true) ) assertEquals( - "2 Tage, 2 Stunden, 30 Minuten, 33 Millisekunden", - NLS.convertDuration(Duration.ofDays(2L).plusHours(2).plusMinutes(30L).plusMillis(33L), true, true) + "2 Tage, 2 Stunden, 30 Minuten, 33 Millisekunden", + NLS.convertDuration(Duration.ofDays(2L).plusHours(2).plusMinutes(30L).plusMillis(33L), true, true) ) assertEquals("", NLS.convertDuration(Duration.ofDays(0L), true, true)) assertEquals("", NLS.convertDuration(null, true, true)) From eb43b99098fd896bb774b14c5ee16188f37652fe Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 4 Sep 2023 10:13:59 +0200 Subject: [PATCH 054/111] translated ValueTest.groovy to kotlin --- .../kotlin/sirius/kernel/commons/ValueTest.kt | 246 ++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 src/test/kotlin/sirius/kernel/commons/ValueTest.kt diff --git a/src/test/kotlin/sirius/kernel/commons/ValueTest.kt b/src/test/kotlin/sirius/kernel/commons/ValueTest.kt new file mode 100644 index 00000000..c947c0e3 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/ValueTest.kt @@ -0,0 +1,246 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import sirius.kernel.nls.NLS +import java.math.BigDecimal +import java.time.LocalDate +import java.time.LocalDateTime +import java.util.* +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +/** + * Tests the [Value] class. + */ +class ValueTest { + + private val DEFAULT_BIG_DECIMAL: BigDecimal = BigDecimal.TEN + + @Test + fun `Test isFilled`() { + assertEquals(true, Value.of(1).isFilled()) + assertEquals(true, Value.of(" ").isFilled()) + assertEquals(true, Value.of("Test").isFilled()) + assertEquals(false, Value.of("").isFilled()) + assertEquals(false, Value.of(null).isFilled()) + + } + + @Test + fun `Test isNumeric`() { + assertEquals(true, Value.of(1).isNumeric()) + assertEquals(true, Value.of("1").isNumeric()) + assertEquals(true, Value.of(-1).isNumeric()) + assertEquals(true, Value.of("-1").isNumeric()) + assertEquals(true, Value.of(0).isNumeric()) + assertEquals(true, Value.of("0").isNumeric()) + assertEquals(true, Value.of(1.1).isNumeric()) + assertEquals(true, Value.of("1.1").isNumeric()) + assertEquals(false, Value.of("1.1.1").isNumeric()) + assertEquals(false, Value.of("").isNumeric()) + assertEquals(false, Value.of(null).isNumeric()) + assertEquals(false, Value.of("Test").isNumeric()) + } + + @Test + fun `Test afterLast`() { + assertEquals("pdf", Value.of("test.x.pdf").afterLast(".")) + } + + @Test + fun `Test beforeLast`() { + assertEquals("test.x", Value.of("test.x.pdf").beforeLast(".")) + } + + @Test + fun `Test afterFirst`() { + assertEquals("x.pdf", Value.of("test.x.pdf").afterFirst(".")) + } + + @Test + fun `Test beforeFirst`() { + assertEquals("test", Value.of("test.x.pdf").beforeFirst(".")) + } + + @Test + fun `Test left`() { + assertEquals("testA", Value.of("testA.testB").left(5)) + assertEquals(".testB", Value.of("testA.testB").left(-5)) + assertEquals("test", Value.of("test").left(5)) + assertEquals("", Value.of(null).left(5)) + } + + @Test + fun `Test right`() { + assertEquals("testB", Value.of("testA.testB").right(5)) + assertEquals("testA.", Value.of("testA.testB").right(-5)) + assertEquals("test", Value.of("test").right(5)) + assertEquals("", Value.of(null).right(5)) + + } + + @Test + fun `Test getBigDecimal`() { + assertEquals(null, Value.of("").getBigDecimal()) + assertEquals(null, Value.of("Not a Number").getBigDecimal()) + assertEquals(BigDecimal.valueOf(42), Value.of("42").getBigDecimal()) + assertEquals(BigDecimal.valueOf(42.0), Value.of("42.0").getBigDecimal()) + assertEquals(BigDecimal.valueOf(42.0), Value.of("42,0").getBigDecimal()) + assertEquals(BigDecimal.valueOf(42), Value.of(42).getBigDecimal()) + assertEquals(BigDecimal.valueOf(42.0), Value.of(42.0).getBigDecimal()) + assertEquals(BigDecimal.valueOf(42), Value.of(Integer.valueOf(42)).getBigDecimal()) + } + + @Test + fun `Test getBigDecimal with default`() { + assertEquals(DEFAULT_BIG_DECIMAL, Value.of("").getBigDecimal(DEFAULT_BIG_DECIMAL)) + assertEquals(DEFAULT_BIG_DECIMAL, Value.of("Not a Number").getBigDecimal(DEFAULT_BIG_DECIMAL)) + assertEquals(BigDecimal.valueOf(42), Value.of("42").getBigDecimal(DEFAULT_BIG_DECIMAL)) + assertEquals(BigDecimal.valueOf(42.0), Value.of("42.0").getBigDecimal(DEFAULT_BIG_DECIMAL)) + assertEquals(BigDecimal.valueOf(42.0), Value.of("42,0").getBigDecimal(DEFAULT_BIG_DECIMAL)) + assertEquals(BigDecimal.valueOf(42), Value.of(42).getBigDecimal(DEFAULT_BIG_DECIMAL)) + assertEquals(BigDecimal.valueOf(42.0), Value.of(42.0).getBigDecimal(DEFAULT_BIG_DECIMAL)) + assertEquals(BigDecimal.valueOf(42), Value.of(Integer.valueOf(42)).getBigDecimal(DEFAULT_BIG_DECIMAL)) + } + + @Test + fun `Test asBoolean with default`() { + assertEquals(false, Value.of("").asBoolean(false)) + assertEquals(false, Value.of("false").asBoolean(false)) + assertEquals(true, Value.of("true").asBoolean(false)) + assertEquals(false, Value.of(false).asBoolean(false)) + assertEquals(true, Value.of(true).asBoolean(false)) + assertEquals(true, Value.of(NLS.get("NLS.yes")).asBoolean(false)) + assertEquals(false, Value.of(NLS.get("NLS.no")).asBoolean(false)) + } + + @Test + fun `Test asLocalDate with default`() { + assertEquals(null, Value.of("").asLocalDate(null)) + assertEquals(LocalDate.of(1994, 5, 8), Value.of(LocalDate.of(1994, 5, 8)).asLocalDate(null)) + assertEquals(LocalDate.of(1994, 5, 8), Value.of(LocalDateTime.of(1994, 5, 8, 10, 30)).asLocalDate(null)) + assertEquals(LocalDate.of(2018, 9, 21), Value.of(1537539103268).asLocalDate(null)) + } + + @Test + fun `Test coerce boolean and without default`() { + assertEquals(false, Value.of("").coerce(Boolean::class.java, null)) + assertEquals(false, Value.of("false").coerce(Boolean::class.java, null)) + assertEquals(true, Value.of("true").coerce(Boolean::class.java, null)) + assertEquals(false, Value.of(false).coerce(Boolean::class.java, null)) + assertEquals(true, Value.of(true).coerce(Boolean::class.java, null)) + } + + @Test + fun `Boxing and retrieving an amount works`() { + assertEquals(Amount.of(0.00001), Value.of(Amount.of(0.00001)).getAmount()) + } + + @Test + fun `Casting to integers works`() { + assertEquals(99999, Value.of(99999).asInt(1234)) + assertEquals(99999, Value.of(99999L).asInt(1234)) + assertEquals(99999, Value.of(99999.0).asInt(1234)) + assertEquals(99999, Value.of((99999).toDouble()).asInt(1234)) + assertEquals(99999, Value.of(Amount.of((99999).toInt())).asInt(1234)) + assertEquals(1234, Value.of(Amount.NOTHING).asInt(1234)) + assertEquals(99999, Value.of("99999").asInt(1234)) + assertEquals(1234, Value.of("Keine Zahl").asInt(1234)) + assertEquals(1234, Value.of(null).asInt(1234)) + } + + @Test + fun `Casting to integer instances works`() { + val boxedInt: Int? = 99999 + assertEquals(boxedInt, Value.of(99999).getInteger()) + assertEquals(boxedInt, Value.of(99999L).getInteger()) + assertEquals(boxedInt, Value.of(99999.0).getInteger()) + assertEquals(boxedInt, Value.of((99999).toDouble()).getInteger()) + assertEquals(boxedInt, Value.of(Amount.of((99999).toInt())).getInteger()) + assertEquals(null, Value.of(Amount.NOTHING).getInteger()) + assertEquals(boxedInt, Value.of("99999").getInteger()) + assertEquals(null, Value.of("Keine Zahl").getInteger()) + assertEquals(null, Value.of(null).getInteger()) + } + + @Test + fun `Casting to longs works`() { + assertEquals(99999L, Value.of(99999).asLong(1234)) + assertEquals(99999L, Value.of(99999L).asLong(1234)) + assertEquals(99999L, Value.of(99999.0).asLong(1234)) + assertEquals(99999L, Value.of((99999).toDouble()).asLong(1234)) + assertEquals(99999L, Value.of(Amount.of((99999).toInt())).asLong(1234)) + assertEquals(1234L, Value.of(Amount.NOTHING).asLong(1234)) + assertEquals(99999L, Value.of("99999").asLong(1234)) + assertEquals(1234L, Value.of("Keine Zahl").asLong(1234)) + assertEquals(1234L, Value.of(null).asLong(1234)) + } + + @Test + fun `Casting to long instances works`() { + val boxedLong: Long? = 99999 + assertEquals(boxedLong, Value.of(99999).getLong()) + assertEquals(boxedLong, Value.of(99999L).getLong()) + assertEquals(boxedLong, Value.of(99999.0).getLong()) + assertEquals(boxedLong, Value.of((99999).toDouble()).getLong()) + assertEquals(boxedLong, Value.of(Amount.of((99999).toInt())).getLong()) + assertEquals(null, Value.of(Amount.NOTHING).getLong()) + assertEquals(boxedLong, Value.of("99999").getLong()) + assertEquals(null, Value.of("Keine Zahl").getLong()) + assertEquals(null, Value.of(null).getLong()) + } + + @Test + fun `map() does not call the mapper on an empty Value`() { + var count = 0 + val mapper: (value: Any) -> String = { value -> + count++ + "" + } + Value.EMPTY.map(mapper) + assertEquals(0, count) + } + + @Test + fun `flatMap() does not call the mapper on an empty Value`() { + var count = 0 + val mapper: (value: Any) -> Optional = { value -> + count++ + Optional.empty() + } + Value.EMPTY.flatMap(mapper) + assertEquals(0, count) + } + + @Test + fun `asOptionalInt must not throw NPE on floats`() { + val value = Value.of(1.1f) + assertDoesNotThrow { + value.asOptionalInt() + } + } + + @Test + fun `append properly handles empty and null`() { + assertEquals("x", Value.EMPTY.append(" ", "x").toString()) + assertEquals("x", Value.of("x").append(" ", null).asString()) + assertEquals("x y", Value.of("x").append(" ", "y").asString()) + } + + @Test + fun `tryAppend only emits an output if the value is filled`() { + assertTrue { Value.EMPTY.tryAppend(" ", "x").isEmptyString() } + assertEquals("x", Value.of("x").tryAppend(" ", null).asString()) + assertEquals("x y", Value.of("x").tryAppend(" ", "y").asString()) + } +} + From 3401ef0a1c592f3c72349e106f561f3e20eced33 Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 4 Sep 2023 10:14:54 +0200 Subject: [PATCH 055/111] deleted ValueSpec.groovy --- .../sirius/kernel/commons/ValueSpec.groovy | 304 ------------------ 1 file changed, 304 deletions(-) delete mode 100644 src/test/java/sirius/kernel/commons/ValueSpec.groovy diff --git a/src/test/java/sirius/kernel/commons/ValueSpec.groovy b/src/test/java/sirius/kernel/commons/ValueSpec.groovy deleted file mode 100644 index 26bfcfe9..00000000 --- a/src/test/java/sirius/kernel/commons/ValueSpec.groovy +++ /dev/null @@ -1,304 +0,0 @@ -package sirius.kernel.commons - -import sirius.kernel.BaseSpecification -import sirius.kernel.nls.NLS - -import java.time.LocalDate -import java.time.LocalDateTime - -class ValueSpec extends BaseSpecification { - - private static final BigDecimal DEFAULT_BIG_DECIMAL = BigDecimal.TEN - - def "Test isFilled"() { - expect: - Value.of(input).isFilled() == output - where: - input | output - 1 | true - " " | true - "Test" | true - "" | false - null | false - } - - def "Test isNumeric"() { - expect: - Value.of(input).isNumeric() == output - where: - input | output - 1 | true - "1" | true - -1 | true - "-1" | true - 0 | true - "0" | true - 1.1 | true - "1.1" | true - "1.1.1" | false - "" | false - null | false - "Test" | false - } - - def "Test afterLast"() { - expect: - Value.of(input).afterLast(separator) == output - where: - input | separator | output - "test.x.pdf" | "." | "pdf" - } - - def "Test beforeLast"() { - expect: - Value.of(input).beforeLast(separator) == output - where: - input | separator | output - "test.x.pdf" | "." | "test.x" - } - - def "Test afterFirst"() { - expect: - Value.of(input).afterFirst(separator) == output - where: - input | separator | output - "test.x.pdf" | "." | "x.pdf" - } - - def "Test beforeFirst"() { - expect: - Value.of(input).beforeFirst(separator) == output - where: - input | separator | output - "test.x.pdf" | "." | "test" - } - - def "Test left"() { - expect: - Value.of(input).left(length) == output - where: - input | length | output - "testA.testB" | 5 | "testA" - "testA.testB" | -5 | ".testB" - "test" | 5 | "test" - null | 5 | "" - } - - def "Test right"() { - expect: - Value.of(input).right(length) == output - where: - input | length | output - "testA.testB" | 5 | "testB" - "testA.testB" | -5 | "testA." - "test" | 5 | "test" - null | 5 | "" - } - - def "Test getBigDecimal"() { - expect: - Value.of(input).getBigDecimal() == output - where: - input | output - "" | null - "Not a Number" | null - "42" | BigDecimal.valueOf(42) - "42.0" | BigDecimal.valueOf(42) - "42,0" | BigDecimal.valueOf(42) - 42 | BigDecimal.valueOf(42) - 42.0 | BigDecimal.valueOf(42) - Integer.valueOf(42) | BigDecimal.valueOf(42) - } - - def "Test getBigDecimal with default"() { - expect: - Value.of(input).getBigDecimal(DEFAULT_BIG_DECIMAL) == output - where: - input | output - "" | DEFAULT_BIG_DECIMAL - "Not a Number" | DEFAULT_BIG_DECIMAL - "42" | BigDecimal.valueOf(42) - "42.0" | BigDecimal.valueOf(42) - "42,0" | BigDecimal.valueOf(42) - 42 | BigDecimal.valueOf(42) - 42.0 | BigDecimal.valueOf(42) - Integer.valueOf(42) | BigDecimal.valueOf(42) - } - - def "Test asBoolean with default"() { - expect: - Value.of(input).asBoolean(false) == output - where: - input | output - "" | false - "false" | false - "true" | true - false | false - true | true - NLS.get("NLS.yes") | true - NLS.get("NLS.no") | false - } - - - def "Test asLocalDate with default"() { - expect: - Value.of(input).asLocalDate(null) == output - where: - input | output - "" | null - LocalDate.of(1994, 5, 8) | LocalDate.of(1994, 5, 8) - LocalDateTime.of(1994, 5, 8, 10, 30) | LocalDate.of(1994, 5, 8) - 1537539103268 | LocalDate.of(2018, 9, 21) - } - - def "Test coerce boolean and without default"() { - expect: - Value.of(input).coerce(boolean.class, null) == output - where: - input | output - "" | false - "false" | false - "true" | true - false | false - true | true - } - - def "Boxing and retrieving an amount works"() { - expect: - Value.of(Amount.of(0.00001)).getAmount() == Amount.of(0.00001) - } - - def "Casting to integers works"() { - expect: - Value.of(input).asInt(1234) == output - where: - input | output - 99999 | 99999 - 99999l | 99999 - 99999.0 | 99999 - new Double(99999) | 99999 - Amount.of(99999) | 99999 - Amount.NOTHING | 1234 - "99999" | 99999 - "Keine Zahl" | 1234 - null | 1234 - } - - def "Casting to integer instances works"() { - expect: - Value.of(input).getInteger() == output - where: - input | output - 99999 | new Integer(99999) - 99999l | new Integer(99999) - 99999.0 | new Integer(99999) - new Double(99999) | new Integer(99999) - Amount.of(99999) | new Integer(99999) - Amount.NOTHING | null - "99999" | new Integer(99999) - "Keine Zahl" | null - null | null - } - - def "Casting to longs works"() { - expect: - Value.of(input).asLong(1234) == output - where: - input | output - 99999 | 99999l - 99999l | 99999l - 99999.0 | 99999l - new Double(99999) | 99999l - Amount.of(99999) | 99999l - Amount.NOTHING | 1234l - "99999" | 99999l - "Keine Zahl" | 1234l - null | 1234l - } - - def "Casting to long instances works"() { - expect: - Value.of(input).getLong() == output - where: - input | output - 99999 | new Long(99999l) - 99999l | new Long(99999l) - 99999.0 | new Long(99999l) - new Double(99999) | new Long(99999l) - Amount.of(99999) | new Long(99999l) - Amount.NOTHING | null - "99999" | new Long(99999l) - "Keine Zahl" | null - null | null - } - - def "Casting to doubles works"() { - expect: - Math.abs(Value.of(input).asDouble(1234.56) - output) < Doubles.EPSILON - where: - input | output - 99999 | 99999.0 - 99999l | 99999.0 - 99999.87 | 99999.87 - new Double(99999.87) | 99999.87 - Amount.of(99999.87) | 99999.87 - Amount.NOTHING | 1234.56 - "99999" | 99999.0 - "99999.87" | 99999.87 - "Keine Zahl" | 1234.56 - null | 1234.56 - } - - def "map() does not call the mapper on an empty Value"() { - given: - def count = 0 - def mapper = { value -> - count++ - "" - } - when: - Value.EMPTY.map(mapper) - then: - count == 0 - } - - def "flatMap() does not call the mapper on an empty Value"() { - given: - def count = 0 - def mapper = { value -> - count++ - Optional.empty() - } - when: - Value.EMPTY.flatMap(mapper) - then: - count == 0 - } - - def "asOptionalInt must not throw NPE on floats"() { - given: - def value = Value.of(1.1f) - when: - value.asOptionalInt() - then: - noExceptionThrown() - } - - def "append properly handles empty/null"() { - expect: - Value.EMPTY.append(" ", "x") == "x" - and: - Value.of("x").append(" ", null).asString() == "x" - and: - Value.of("x").append(" ", "y").asString() == "x y" - } - - def "tryAppend only emits an output if the value is filled"() { - expect: - Value.EMPTY.tryAppend(" ", "x").isEmptyString() - and: - Value.of("x").tryAppend(" ", null).asString() == "x" - and: - Value.of("x").tryAppend(" ", "y").asString() == "x y" - } -} From 53e8e9261528c2d63bc506281b9ad5f0bb100357 Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 4 Sep 2023 11:20:25 +0200 Subject: [PATCH 056/111] removed wild card imports, removed assertDoesNotThrow --- src/test/kotlin/sirius/kernel/commons/ValueTest.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/test/kotlin/sirius/kernel/commons/ValueTest.kt b/src/test/kotlin/sirius/kernel/commons/ValueTest.kt index c947c0e3..c822dbad 100644 --- a/src/test/kotlin/sirius/kernel/commons/ValueTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/ValueTest.kt @@ -9,12 +9,11 @@ package sirius.kernel.commons import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertDoesNotThrow import sirius.kernel.nls.NLS import java.math.BigDecimal import java.time.LocalDate import java.time.LocalDateTime -import java.util.* +import java.util.Optional import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -224,9 +223,8 @@ class ValueTest { @Test fun `asOptionalInt must not throw NPE on floats`() { val value = Value.of(1.1f) - assertDoesNotThrow { - value.asOptionalInt() - } + value.asOptionalInt() + } @Test From 1039c11781a44f0ff09c754daddb0f47a0edd1a4 Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 4 Sep 2023 11:21:45 +0200 Subject: [PATCH 057/111] removed unnecessary line --- src/test/kotlin/sirius/kernel/commons/ValueTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/kotlin/sirius/kernel/commons/ValueTest.kt b/src/test/kotlin/sirius/kernel/commons/ValueTest.kt index c822dbad..73c70a21 100644 --- a/src/test/kotlin/sirius/kernel/commons/ValueTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/ValueTest.kt @@ -224,7 +224,6 @@ class ValueTest { fun `asOptionalInt must not throw NPE on floats`() { val value = Value.of(1.1f) value.asOptionalInt() - } @Test From b15308271a6c5cdcc074285e3cd18c66f7daee6f Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 4 Sep 2023 16:25:51 +0200 Subject: [PATCH 058/111] Migration of URLBuilderSpec to kotlin --- .../sirius/kernel/commons/URLBuilderTest.kt | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt diff --git a/src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt b/src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt new file mode 100644 index 00000000..8b6dd19e --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt @@ -0,0 +1,210 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import kotlin.test.assertEquals + +/** + * Tests the [URLBuilder] class. + */ +class URLBuilderTest { + + @Test + fun `baseURL is handled correctly`() { + + var urlBuilder = URLBuilder("http://sirius-lib.net") + assertEquals("http://sirius-lib.net", urlBuilder.build()) + + urlBuilder = URLBuilder("http://sirius-lib.net/") + assertEquals("http://sirius-lib.net", urlBuilder.build()) + + urlBuilder = URLBuilder("sirius-lib.net") + assertEquals("sirius-lib.net", urlBuilder.build()) + + urlBuilder = URLBuilder("sirius-lib.net/") + assertEquals("sirius-lib.net", urlBuilder.build()) + + urlBuilder = URLBuilder("www.sirius-lib.net") + assertEquals("www.sirius-lib.net", urlBuilder.build()) + + urlBuilder = URLBuilder("www.sirius-lib.net/") + assertEquals("www.sirius-lib.net", urlBuilder.build()) + + urlBuilder = URLBuilder("www.sirius-lib.net/example") + assertEquals("www.sirius-lib.net/example", urlBuilder.build()) + + urlBuilder = URLBuilder("www.sirius-lib.net/example/") + assertEquals("www.sirius-lib.net/example", urlBuilder.build()) + + urlBuilder = URLBuilder("http://www.sirius-lib.net") + assertEquals("http://www.sirius-lib.net", urlBuilder.build()) + + urlBuilder = URLBuilder("http://www.sirius-lib.net/") + assertEquals("http://www.sirius-lib.net", urlBuilder.build()) + + urlBuilder = URLBuilder("localhost:80") + assertEquals("localhost:80", urlBuilder.build()) + + urlBuilder = URLBuilder("localhost:80/") + assertEquals("localhost:80", urlBuilder.build()) + + urlBuilder = URLBuilder("127.0.0.1:80/") + assertEquals("127.0.0.1:80", urlBuilder.build()) + } + + @Test + fun `baseURL creation with protocol and host constructor is working`() { + + var urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + assertEquals("http://sirius-lib.net", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTPS, "sirius-lib.net") + assertEquals("https://sirius-lib.net", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "www.sirius-lib.net") + assertEquals("http://www.sirius-lib.net", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "www.sirius-lib.net/example") + assertEquals("http://www.sirius-lib.net/example", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTPS, "www.sirius-lib.net/example") + assertEquals("https://www.sirius-lib.net/example", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "localhost:80") + assertEquals("http://localhost:80", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTPS, "localhost:80") + assertEquals("https://localhost:80", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "127.0.0.1:80") + assertEquals("http://127.0.0.1:80", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTPS, "127.0.0.1:80") + assertEquals("https://127.0.0.1:80", urlBuilder.build()) + } + + @Test + fun `adding a single part is handled correctly`() { + var urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addSafePart("") + assertEquals("http://sirius-lib.net", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addSafePart("example") + assertEquals("http://sirius-lib.net/example", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addSafePart("/example/") + assertEquals("http://sirius-lib.net/example", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addSafePart("file.jpg") + assertEquals("http://sirius-lib.net/file.jpg", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addSafePart("example/second/file.jpg") + assertEquals("http://sirius-lib.net/example/second/file.jpg", urlBuilder.build()) + + } + + @Test + fun `adding multiple parts through varargs is handled correctly`() { + var urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addSafeParts("", "", "", "") + assertEquals("http://sirius-lib.net", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addSafeParts("example", "/", "file.jpg") + assertEquals("http://sirius-lib.net/example/file.jpg", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addSafeParts("very", "long", "example") + assertEquals("http://sirius-lib.net/very/long/example", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addSafeParts("/very/", "//long//", "/example/") + assertEquals("http://sirius-lib.net/very/long/example", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addSafeParts("example", "", "/", "", "file.jpg") + assertEquals("http://sirius-lib.net/example/file.jpg", urlBuilder.build()) + } + + @Test + fun `the standard method for adding parameters encodes the value`() { + val urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addParameter("test", "test value") + assertEquals("http://sirius-lib.net?test=test+value", urlBuilder.build()) + } + + @Test + fun `a single parameter is added and url encoded correctly`() { + var urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addParameter("test", "", true) + assertEquals("http://sirius-lib.net?test=", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addParameter("test", "", false) + assertEquals("http://sirius-lib.net?test=", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addParameter("test", "value", true) + assertEquals("http://sirius-lib.net?test=value", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addParameter("test", "value", false) + assertEquals("http://sirius-lib.net?test=value", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addParameter("test", "test value", true) + assertEquals("http://sirius-lib.net?test=test+value", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addParameter("test", "test+value", false) + assertEquals("http://sirius-lib.net?test=test+value", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addParameter("test", "#value", true) + assertEquals("http://sirius-lib.net?test=%23value", urlBuilder.build()) + + urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addParameter("test", "%23value", false) + assertEquals("http://sirius-lib.net?test=%23value", urlBuilder.build()) + } + + @Test + fun `multiple parameters are added correctly`() { + val urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + + urlBuilder.addParameter("test1", "value1") + urlBuilder.addParameter("test2", "value2") + urlBuilder.addParameter("test3", "value3") + assertEquals("http://sirius-lib.net?test1=value1&test2=value2&test3=value3", urlBuilder.build()) + + } + + @Test + fun `can't add parts after a parameter has been added`() { + assertThrows { + val urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") + urlBuilder.addParameter("test", "value") + urlBuilder.addSafePart("late") + } + + } + + @Test + fun `baseurl with query part can be extended with another parameter`() { + val urlBuilder = URLBuilder("http://sirius-lib.net?already=there") + urlBuilder.addParameter("test", "test value") + assertEquals("http://sirius-lib.net?already=there&test=test+value", urlBuilder.build()) + } +} From a105b6bdcd615590cfeac6b252f3bd335841aa5d Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 4 Sep 2023 16:28:01 +0200 Subject: [PATCH 059/111] Deleted URLBuilderSpec --- .../kernel/commons/URLBuilderSpec.groovy | 137 ------------------ 1 file changed, 137 deletions(-) delete mode 100644 src/test/java/sirius/kernel/commons/URLBuilderSpec.groovy diff --git a/src/test/java/sirius/kernel/commons/URLBuilderSpec.groovy b/src/test/java/sirius/kernel/commons/URLBuilderSpec.groovy deleted file mode 100644 index 2238a0ae..00000000 --- a/src/test/java/sirius/kernel/commons/URLBuilderSpec.groovy +++ /dev/null @@ -1,137 +0,0 @@ -package sirius.kernel.commons - -import sirius.kernel.BaseSpecification - -class URLBuilderSpec extends BaseSpecification { - - def "baseURL is handled correctly"() { - when: - def urlBuilder = new URLBuilder(baseUrl) - then: - urlBuilder.build() == expectedUrl - - where: - baseUrl | expectedUrl - "http://sirius-lib.net" | "http://sirius-lib.net" - "http://sirius-lib.net/" | "http://sirius-lib.net" - "sirius-lib.net" | "sirius-lib.net" - "sirius-lib.net/" | "sirius-lib.net" - "www.sirius-lib.net" | "www.sirius-lib.net" - "www.sirius-lib.net/" | "www.sirius-lib.net" - "www.sirius-lib.net/example" | "www.sirius-lib.net/example" - "www.sirius-lib.net/example/" | "www.sirius-lib.net/example" - "http://www.sirius-lib.net" | "http://www.sirius-lib.net" - "http://www.sirius-lib.net/" | "http://www.sirius-lib.net" - "localhost:80" | "localhost:80" - "localhost:80/" | "localhost:80" - "127.0.0.1:80" | "127.0.0.1:80" - "127.0.0.1:80/" | "127.0.0.1:80" - } - - def "baseURL creation with protocol / host constructor is working"() { - when: - def urlBuilder = new URLBuilder(protocol, host) - then: - urlBuilder.build() == expectedUrl - - where: - protocol | host | expectedUrl - URLBuilder.PROTOCOL_HTTP | "sirius-lib.net" | "http://sirius-lib.net" - URLBuilder.PROTOCOL_HTTPS | "sirius-lib.net" | "https://sirius-lib.net" - URLBuilder.PROTOCOL_HTTP | "www.sirius-lib.net" | "http://www.sirius-lib.net" - URLBuilder.PROTOCOL_HTTPS | "www.sirius-lib.net" | "https://www.sirius-lib.net" - URLBuilder.PROTOCOL_HTTP | "www.sirius-lib.net/example" | "http://www.sirius-lib.net/example" - URLBuilder.PROTOCOL_HTTPS | "www.sirius-lib.net/example" | "https://www.sirius-lib.net/example" - URLBuilder.PROTOCOL_HTTP | "localhost:80" | "http://localhost:80" - URLBuilder.PROTOCOL_HTTPS | "localhost:80" | "https://localhost:80" - URLBuilder.PROTOCOL_HTTP | "127.0.0.1:80" | "http://127.0.0.1:80" - URLBuilder.PROTOCOL_HTTPS | "127.0.0.1:80" | "https://127.0.0.1:80" - } - - def "adding a single part is handled correctly"() { - when: - def urlBuilder = new URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") - urlBuilder.addSafePart(part) - then: - urlBuilder.build() == expectedUrl - - where: - part | expectedUrl - "" | "http://sirius-lib.net" - "example" | "http://sirius-lib.net/example" - "/example/" | "http://sirius-lib.net/example" - "file.jpg" | "http://sirius-lib.net/file.jpg" - "example/second/file.jpg" | "http://sirius-lib.net/example/second/file.jpg" - } - - def "adding multiple parts through varargs is handled correctly"() { - when: - def urlBuilder = new URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") - urlBuilder.addSafeParts(*part) - then: - urlBuilder.build() == expectedUrl - - where: - part | expectedUrl - ["", "", "", ""] | "http://sirius-lib.net" - ["example", "/", "file.jpg"] | "http://sirius-lib.net/example/file.jpg" - ["very", "long", "example"] | "http://sirius-lib.net/very/long/example" - ["/very/", "//long//", "/example/"] | "http://sirius-lib.net/very/long/example" - ["example", "", "/", "", "file.jpg"] | "http://sirius-lib.net/example/file.jpg" - } - - def "the standard method for adding parameters encodes the value"() { - when: - def urlBuilder = new URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") - urlBuilder.addParameter("test", "test value") - then: - urlBuilder.build() == "http://sirius-lib.net?test=test+value" - } - - def "a single parameter is added and url encoded correctly"() { - when: - def urlBuilder = new URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") - urlBuilder.addParameter(key, value, urlencode) - then: - urlBuilder.build() == expectedUrl - - where: - key | value | urlencode | expectedUrl - "test" | "" | true | "http://sirius-lib.net?test=" - "test" | "" | false | "http://sirius-lib.net?test=" - "test" | "value" | true | "http://sirius-lib.net?test=value" - "test" | "value" | false | "http://sirius-lib.net?test=value" - "test" | "test value" | true | "http://sirius-lib.net?test=test+value" - "test" | "test+value" | false | "http://sirius-lib.net?test=test+value" - "test" | "#value" | true | "http://sirius-lib.net?test=%23value" - "test" | "%23value" | false | "http://sirius-lib.net?test=%23value" - } - - def "multiple parameters are added correctly"() { - when: - def urlBuilder = new URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") - urlBuilder.addParameter("test1", "value1") - urlBuilder.addParameter("test2", "value2") - urlBuilder.addParameter("test3", "value3") - then: - urlBuilder.build() == "http://sirius-lib.net?test1=value1&test2=value2&test3=value3" - } - - def "can't add parts after a parameter has been added"() { - when: - def urlBuilder = new URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") - urlBuilder.addParameter("test", "value") - urlBuilder.addSafePart("late") - then: - thrown IllegalStateException - } - - def "baseurl with query part can be extended with another parameter"() { - when: - def urlBuilder = new URLBuilder("http://sirius-lib.net?already=there") - urlBuilder.addParameter("test", "test value") - then: - urlBuilder.build() == "http://sirius-lib.net?already=there&test=test+value" - } - -} From 73989425e130b5d501bb03e8f49842a60eed528f Mon Sep 17 00:00:00 2001 From: MOOOOOSER <92032959+MOOOOOSER@users.noreply.github.com> Date: Mon, 4 Sep 2023 16:40:22 +0200 Subject: [PATCH 060/111] Update src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Florian Häfner <53555813+fhaScireum@users.noreply.github.com> --- src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt b/src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt index 8b6dd19e..3d6aa78d 100644 --- a/src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt @@ -19,7 +19,6 @@ class URLBuilderTest { @Test fun `baseURL is handled correctly`() { - var urlBuilder = URLBuilder("http://sirius-lib.net") assertEquals("http://sirius-lib.net", urlBuilder.build()) From 64ec81727c623edf0e0eb579556af542ebec8f5d Mon Sep 17 00:00:00 2001 From: MOOOOOSER <92032959+MOOOOOSER@users.noreply.github.com> Date: Mon, 4 Sep 2023 16:40:50 +0200 Subject: [PATCH 061/111] Update src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Florian Häfner <53555813+fhaScireum@users.noreply.github.com> --- src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt b/src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt index 3d6aa78d..14e92692 100644 --- a/src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt @@ -61,7 +61,6 @@ class URLBuilderTest { @Test fun `baseURL creation with protocol and host constructor is working`() { - var urlBuilder = URLBuilder(URLBuilder.PROTOCOL_HTTP, "sirius-lib.net") assertEquals("http://sirius-lib.net", urlBuilder.build()) From 58a7d58c944e4f98286dfb0380bbb219b4a8c4b2 Mon Sep 17 00:00:00 2001 From: MOOOOOSER <92032959+MOOOOOSER@users.noreply.github.com> Date: Mon, 4 Sep 2023 16:41:44 +0200 Subject: [PATCH 062/111] Update src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Florian Häfner <53555813+fhaScireum@users.noreply.github.com> --- src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt b/src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt index 14e92692..c18a0d00 100644 --- a/src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt @@ -186,7 +186,6 @@ class URLBuilderTest { urlBuilder.addParameter("test2", "value2") urlBuilder.addParameter("test3", "value3") assertEquals("http://sirius-lib.net?test1=value1&test2=value2&test3=value3", urlBuilder.build()) - } @Test From a0c5a82d3d69a1e88ead379c70ea4b1ab1cfb5f0 Mon Sep 17 00:00:00 2001 From: MOOOOOSER <92032959+MOOOOOSER@users.noreply.github.com> Date: Mon, 4 Sep 2023 16:41:50 +0200 Subject: [PATCH 063/111] Update src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Florian Häfner <53555813+fhaScireum@users.noreply.github.com> --- src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt b/src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt index c18a0d00..bc9b205f 100644 --- a/src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/URLBuilderTest.kt @@ -195,7 +195,6 @@ class URLBuilderTest { urlBuilder.addParameter("test", "value") urlBuilder.addSafePart("late") } - } @Test From bd4a776bc79c0c8c07245299f80a23aa8d3dcf40 Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Tue, 5 Sep 2023 16:18:13 +0200 Subject: [PATCH 064/111] moved content from amountSpec to AmountTest.kt --- .../sirius/kernel/commons/AmountTest.kt | 350 ++++++++++++++++++ 1 file changed, 350 insertions(+) create mode 100644 src/test/kotlin/sirius/kernel/commons/AmountTest.kt diff --git a/src/test/kotlin/sirius/kernel/commons/AmountTest.kt b/src/test/kotlin/sirius/kernel/commons/AmountTest.kt new file mode 100644 index 00000000..87455619 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/AmountTest.kt @@ -0,0 +1,350 @@ +package sirius.kernel.commons + +import sirius.kernel.BaseSpecification +import sirius.kernel.async.CallContext + +import java.math.RoundingMode +import java.util.function.Supplier + +class AmountSpec extends BaseSpecification { + + def "predicates are evaluated correctly"() { + expect: + Amount.NOTHING.isEmpty() + Amount.NOTHING.isZeroOrNull() + Amount.ZERO.isZeroOrNull() + Amount.ZERO.isZero() + Amount.MINUS_ONE.isNonZero() + Amount.MINUS_ONE.isNegative() + Amount.TEN.isPositive() + Amount.TEN > Amount.ONE + } + + def "Amount.of converts various types correctly"() { + when: + CallContext.getCurrent().setLanguage("en") + then: + Amount.ONE == x + + where: + x << [Amount.of(1.0D), + Amount.of(1L), + Amount.of(Integer.valueOf(1)), + Amount.of(Long.valueOf(1L)), + Amount.of(Double.valueOf(1D)), + Amount.ofMachineString("1.0"), + Amount.ofUserString("1.0")] + } + + def "ofMachineString works correctly"() { + expect: + Amount.ofMachineString(input) == output + + where: + input | output + "1" | Amount.ONE + "0.1" | Amount.ONE.divideBy(Amount.TEN) + "10" | Amount.TEN + } + + def "Computations with NOTHING result in expected values"() { + expect: + Amount.ONE == Amount.ONE.add(Amount.NOTHING) + Amount.NOTHING == Amount.NOTHING.add(Amount.ONE) + Amount.NOTHING == Amount.NOTHING.add(Amount.NOTHING) + Amount.of(2) == Amount.ONE.add(Amount.ONE) + Amount.MINUS_ONE == Amount.ONE.subtract(Amount.of(2)) + Amount.ONE_HUNDRED == Amount.TEN.times(Amount.TEN) + Amount.NOTHING == Amount.TEN.times(Amount.NOTHING) + Amount.NOTHING == Amount.NOTHING.times(Amount.TEN) + Amount.NOTHING == Amount.ONE.divideBy(Amount.ZERO) + Amount.NOTHING == Amount.ONE.divideBy(Amount.NOTHING) + Amount.NOTHING == Amount.NOTHING.divideBy(Amount.ONE) + Amount.TEN == Amount.ONE_HUNDRED.divideBy(Amount.TEN) + Amount.of(2) == Amount.ONE.increasePercent(Amount.ONE_HUNDRED) + Amount.of(0.5) == Amount.ONE.decreasePercent(Amount.of(50)) + } + + def "fill and orElseGet are only evaluated if no value is present"() { + expect: + Amount.TEN == Amount.NOTHING.fill(Amount.TEN) + Amount.TEN == Amount.TEN.fill(Amount.ONE) + Amount.NOTHING.orElseGet(new Supplier() { + @Override + Amount get() { + return Amount.TEN + } + }) == Amount.TEN + } + + def "helper functions compute correct values"() { + expect: + Amount.TEN == Amount.TEN.percentageOf(Amount.ONE_HUNDRED) + Amount.ONE_HUNDRED == Amount.TEN.percentageDifferenceOf(Amount.of(5)) + Amount.of(-50) == Amount.of(5).percentageDifferenceOf(Amount.TEN) + Amount.of(0.5) == Amount.of(50).asDecimal() + Amount.ONE_HUNDRED.getDigits() == 3 + Amount.ONE.getDigits() == 1 + Amount.ONE_HUNDRED.subtract(Amount.ONE).getDigits() == 2 + Amount.of(477).getDigits() == 3 + } + + def "rounding works as expected"() { + expect: + "1.23" == Amount.ofMachineString("1.23223").round(2, RoundingMode.HALF_UP).toMachineString() + "1.232" == Amount.ofMachineString("1.23223").round(3, RoundingMode.HALF_UP).toMachineString() + "1.2" == Amount.ofMachineString("1.23223").round(1, RoundingMode.HALF_UP).toMachineString() + } + + def "formatting works as expected"() { + when: + CallContext.getCurrent().setLanguage("en") + then: + Amount.ofMachineString("0.1").toPercent().toPercentString() == "10 %" + Amount.of(1.23).toRoundedString() == "1" + Amount.of(1.00).toSmartRoundedString(NumberFormat.TWO_DECIMAL_PLACES).asString() == "1" + Amount.of(1.00).toString(NumberFormat.TWO_DECIMAL_PLACES).asString() == "1.00" + Amount.of(1.23).toSmartRoundedString(NumberFormat.TWO_DECIMAL_PLACES).asString() == "1.23" + Amount.of(0.012).toScientificString(0, "") == "12 m" + Amount.of(1200).toScientificString(1, "") == "1.2 K" + } + + def "min selects the lower value and handles null and NOTHING gracefully"() { + expect: + Amount.ONE.min(Amount.TEN) == Amount.ONE + Amount.TEN.min(Amount.ONE) == Amount.ONE + Amount.NOTHING.min(Amount.ONE) == Amount.ONE + Amount.ONE.min(Amount.NOTHING) == Amount.ONE + Amount.NOTHING.min(Amount.NOTHING) == Amount.NOTHING + Amount.TEN.min(null) == Amount.TEN + Amount.ZERO.min(Amount.NOTHING) == Amount.ZERO + } + + def "max selects the higher value and handles null and NOTHING gracefully"() { + expect: + Amount.ONE.max(Amount.TEN) == Amount.TEN + Amount.TEN.max(Amount.ONE) == Amount.TEN + Amount.NOTHING.max(Amount.ONE) == Amount.ONE + Amount.ONE.max(Amount.NOTHING) == Amount.ONE + Amount.NOTHING.max(Amount.NOTHING) == Amount.NOTHING + Amount.TEN.max(null) == Amount.TEN + Amount.ZERO.max(Amount.NOTHING) == Amount.ZERO + } + + def "compare returns which amount is higher"() { + expect: + Amount.NOTHING.compareTo(Amount.NOTHING) == 0 + Amount.ONE.compareTo(Amount.NOTHING) > 0 + Amount.NOTHING.compareTo(Amount.ONE) < 0 + Amount.ONE.compareTo(Amount.ONE) == 0 + Amount.ONE.compareTo(Amount.MINUS_ONE) > 0 + Amount.MINUS_ONE.compareTo(Amount.ONE) < 0 + } + + def "boilerplace comparators work"() { + expect: + Amount.ONE.isGreaterThan(Amount.NOTHING) + Amount.ONE.isGreaterThanOrEqual(Amount.NOTHING) + Amount.NOTHING.isLessThan(Amount.ONE) + Amount.NOTHING.isLessThanOrEqual(Amount.ONE) + Amount.ONE.isGreaterThanOrEqual(Amount.ONE) + Amount.ONE.isLessThanOrEqual(Amount.ONE) + Amount.ONE.isGreaterThan(Amount.MINUS_ONE) + Amount.MINUS_ONE.isLessThan(Amount.ONE) + } + + def "add() works as expected"() { + given: + Amount amountA = a instanceof Amount ? a : Amount.of(a) + Amount amountB = b instanceof Amount ? b : Amount.of(b) + Amount amountResult = result instanceof Amount ? result : Amount.of(result) + expect: + amountA.add(amountB) == amountResult + where: + a | b | result + 4.2 | 42 | 46.2 + 42 | 4.2 | 46.2 + Amount.ZERO | 42 | 42 + 42 | Amount.ZERO | 42 + Amount.NOTHING | 42 | Amount.NOTHING + 42 | Amount.NOTHING | 42 + } + + def "subtract() works as expected"() { + given: + Amount amountA = a instanceof Amount ? a : Amount.of(a) + Amount amountB = b instanceof Amount ? b : Amount.of(b) + Amount amountResult = result instanceof Amount ? result : Amount.of(result) + expect: + amountA.subtract(amountB) == amountResult + where: + a | b | result + 4.2 | 42 | -37.8 + 42 | 4.2 | 37.8 + Amount.ZERO | 42 | -42 + 42 | Amount.ZERO | 42 + Amount.NOTHING | 42 | Amount.NOTHING + 42 | Amount.NOTHING | 42 + } + + def "times() works as expected"() { + given: + Amount amountA = a instanceof Amount ? a : Amount.of(a) + Amount amountB = b instanceof Amount ? b : Amount.of(b) + Amount amountResult = result instanceof Amount ? result : Amount.of(result) + expect: + amountA.times(amountB) == amountResult + where: + a | b | result + 4.2 | 42 | 176.4 + 42 | 4.2 | 176.4 + Amount.ZERO | 42 | Amount.ZERO + 42 | Amount.ZERO | Amount.ZERO + Amount.NOTHING | 42 | Amount.NOTHING + 42 | Amount.NOTHING | Amount.NOTHING + } + + def "divideBy() works as expected"() { + given: + Amount amountA = a instanceof Amount ? a : Amount.of(a) + Amount amountB = b instanceof Amount ? b : Amount.of(b) + Amount amountResult = result instanceof Amount ? result : Amount.of(result) + expect: + amountA.divideBy(amountB) == amountResult + where: + a | b | result + 4.2 | 42 | 0.1 + 42 | 4.2 | Amount.TEN + Amount.ZERO | 42 | Amount.ZERO + 42 | Amount.ZERO | Amount.NOTHING + Amount.NOTHING | 42 | Amount.NOTHING + 42 | Amount.NOTHING | Amount.NOTHING + } + + def "negate() works as expected"() { + given: + Amount amountA = a instanceof Amount ? a : Amount.of(a) + Amount amountResult = result instanceof Amount ? result : Amount.of(result) + expect: + amountA.negate() == amountResult + where: + a | result + 4.2 | -4.2 + -4.2 | 4.2 + 42 | -42 + Amount.ZERO | Amount.ZERO + -0 | Amount.ZERO + Amount.NOTHING | Amount.NOTHING + } + + def "increasePercent() works as expected"() { + given: + Amount amountA = a instanceof Amount ? a : Amount.of(a) + Amount amountB = b instanceof Amount ? b : Amount.of(b) + Amount amountResult = result instanceof Amount ? result : Amount.of(result) + expect: + amountA.increasePercent(amountB) == amountResult + where: + a | b | result + 4.2 | Amount.TEN | 4.62 + Amount.ZERO | 42 | Amount.ZERO + 42 | Amount.ZERO | 42 + Amount.NOTHING | 42 | Amount.NOTHING + 42 | Amount.NOTHING | 42 + } + + def "decreasePercent() works as expected"() { + given: + Amount amountA = a instanceof Amount ? a : Amount.of(a) + Amount amountB = b instanceof Amount ? b : Amount.of(b) + Amount amountResult = result instanceof Amount ? result : Amount.of(result) + expect: + amountA.decreasePercent(amountB) == amountResult + where: + a | b | result + 4.2 | Amount.TEN | 3.78 + Amount.ZERO | 42 | Amount.ZERO + 42 | Amount.ZERO | 42 + Amount.NOTHING | 42 | Amount.NOTHING + 42 | Amount.NOTHING | 42 + } + + def "percentageOf() works as expected"() { + given: + Amount amountA = a instanceof Amount ? a : Amount.of(a) + Amount amountB = b instanceof Amount ? b : Amount.of(b) + Amount amountResult = result instanceof Amount ? result : Amount.of(result) + expect: + amountA.percentageOf(amountB) == amountResult + where: + a | b | result + 4.2 | 42 | Amount.TEN + Amount.ZERO | 42 | Amount.ZERO + 42 | Amount.ZERO | Amount.NOTHING + Amount.NOTHING | 42 | Amount.NOTHING + 42 | Amount.NOTHING | Amount.NOTHING + } + + def "percentageDifferenceOf() works as expected"() { + given: + Amount amountA = a instanceof Amount ? a : Amount.of(a) + Amount amountB = b instanceof Amount ? b : Amount.of(b) + Amount amountResult = result instanceof Amount ? result : Amount.of(result) + expect: + amountA.percentageDifferenceOf(amountB) == amountResult + where: + a | b | result + 4.62 | 4.2 | Amount.TEN + Amount.ZERO | 42 | -100 + 42 | Amount.ZERO | Amount.NOTHING + Amount.NOTHING | 42 | Amount.NOTHING + 42 | Amount.NOTHING | Amount.NOTHING + } + + def "toPercent() works as expected"() { + given: + Amount amountA = a instanceof Amount ? a : Amount.of(a) + Amount amountResult = result instanceof Amount ? result : Amount.of(result) + expect: + amountA.toPercent() == amountResult + where: + a | result + 0.42 | 42 + Amount.ONE | Amount.ONE_HUNDRED + 2 | 200 + Amount.ZERO | Amount.ZERO + Amount.NOTHING | Amount.NOTHING + } + + def "asDecimal() works as expected"() { + given: + Amount amountA = a instanceof Amount ? a : Amount.of(a) + Amount amountResult = result instanceof Amount ? result : Amount.of(result) + expect: + amountA.asDecimal() == amountResult + where: + a | result + 42 | 0.42 + Amount.ONE_HUNDRED | Amount.ONE + 200 | 2 + Amount.ZERO | Amount.ZERO + Amount.NOTHING | Amount.NOTHING + } + + def "remainder() works as expected"() { + given: + Amount amountA = a instanceof Amount ? a : Amount.of(a) + Amount amountB = b instanceof Amount ? b : Amount.of(b) + Amount amountResult = result instanceof Amount ? result : Amount.of(result) + expect: + amountA.remainder(amountB) == amountResult + where: + a | b | result + 10 | 2 | 0 + 10 | 3 | 1 + 10 | 0 | Amount.NOTHING + 0 | 10 | 0 + Amount.NOTHING | 10 | Amount.NOTHING + 10 | Amount.NOTHING | Amount.NOTHING + } +} From f1794747f9190e6cb4a47c2aa9de0979e68475d3 Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Tue, 5 Sep 2023 16:21:12 +0200 Subject: [PATCH 065/111] converted tests to kotlin --- .../sirius/kernel/commons/AmountTest.kt | 824 +++++++++++------- 1 file changed, 528 insertions(+), 296 deletions(-) diff --git a/src/test/kotlin/sirius/kernel/commons/AmountTest.kt b/src/test/kotlin/sirius/kernel/commons/AmountTest.kt index 87455619..5c584b5e 100644 --- a/src/test/kotlin/sirius/kernel/commons/AmountTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/AmountTest.kt @@ -1,350 +1,582 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + package sirius.kernel.commons -import sirius.kernel.BaseSpecification +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import sirius.kernel.SiriusExtension import sirius.kernel.async.CallContext - import java.math.RoundingMode -import java.util.function.Supplier - -class AmountSpec extends BaseSpecification { - - def "predicates are evaluated correctly"() { - expect: - Amount.NOTHING.isEmpty() - Amount.NOTHING.isZeroOrNull() - Amount.ZERO.isZeroOrNull() - Amount.ZERO.isZero() - Amount.MINUS_ONE.isNonZero() - Amount.MINUS_ONE.isNegative() - Amount.TEN.isPositive() - Amount.TEN > Amount.ONE +import java.util.stream.Stream +import kotlin.test.assertEquals +import kotlin.test.assertTrue + + +/** + * Tests the [Amount] class. + */ +@ExtendWith(SiriusExtension::class) +class AmountTest { + + companion object { + @JvmStatic + private fun `generator for add() works as expected`(): Stream? { + return Stream.of( + Arguments.of( + 4.2, 42, 46.2 + ), + Arguments.of( + 42, 4.2, 46.2 + ), + Arguments.of( + Amount.ZERO, 42, 42 + ), + Arguments.of( + 42, Amount.ZERO, 42 + ), + Arguments.of( + Amount.NOTHING, 42, Amount.NOTHING + ), + Arguments.of( + 42, Amount.NOTHING, 42 + ), + ) + } + + @JvmStatic + private fun `generator for subtract() works as expected`(): Stream? { + return Stream.of( + Arguments.of( + 4.2, 42, -37.8 + ), + Arguments.of( + 42, 4.2, 37.8 + ), + Arguments.of( + Amount.ZERO, 42, -42 + ), + Arguments.of( + 42, Amount.ZERO, 42 + ), + Arguments.of( + Amount.NOTHING, 42, Amount.NOTHING + ), + Arguments.of( + 42, Amount.NOTHING, 42 + ), + ) + } + + @JvmStatic + private fun `generator for times() works as expected`(): Stream? { + return Stream.of( + Arguments.of( + 4.2, 42, 176.4 + ), + Arguments.of( + 42, 4.2, 176.4 + ), + Arguments.of(Amount.ZERO, 42, Amount.ZERO), + Arguments.of( + 42, Amount.ZERO, Amount.ZERO + ), + Arguments.of( + Amount.NOTHING, 42, Amount.NOTHING + ), + Arguments.of( + 42, Amount.NOTHING, Amount.NOTHING + ), + ) + } + + @JvmStatic + private fun `generator for divideBy() works as expected`(): Stream? { + return Stream.of( + Arguments.of( + 4.2, 42, 0.1 + ), + Arguments.of( + 42, 4.2, Amount.TEN + ), + Arguments.of( + Amount.ZERO, 42, Amount.ZERO + ), + Arguments.of( + 42, Amount.ZERO, Amount.NOTHING + ), + Arguments.of( + Amount.NOTHING, 42, Amount.NOTHING + ), + Arguments.of( + 42, Amount.NOTHING, Amount.NOTHING + ), + ) + } + + @JvmStatic + private fun `generator for negate() works as expected`(): Stream? { + return Stream.of( + Arguments.of( + 4.2, -4.2 + ), + Arguments.of( + -4.2, 4.2 + ), + Arguments.of( + 42, -42 + ), + Arguments.of( + Amount.ZERO, Amount.ZERO + ), + Arguments.of( + -0, Amount.ZERO + ), + Arguments.of( + Amount.NOTHING, Amount.NOTHING + ) + ) + } + + @JvmStatic + private fun `generator for increasePercent() works as expected`(): Stream? { + return Stream.of( + Arguments.of( + 4.2, Amount.TEN, 4.62 + ), + Arguments.of( + Amount.ZERO, 42, Amount.ZERO + ), + Arguments.of( + 42, Amount.ZERO, 42 + ), + Arguments.of( + Amount.NOTHING, 42, Amount.NOTHING + ), + Arguments.of( + 42, Amount.NOTHING, 42 + ), + ) + } + + @JvmStatic + private fun `generator for decreasePercent() works as expected`(): Stream? { + return Stream.of( + Arguments.of( + 4.2, Amount.TEN, 3.78 + ), + Arguments.of( + Amount.ZERO, 42, Amount.ZERO + ), + Arguments.of( + 42, Amount.ZERO, 42 + ), + Arguments.of( + Amount.NOTHING, 42, Amount.NOTHING + ), + Arguments.of( + 42, Amount.NOTHING, 42 + ), + ) + } + + @JvmStatic + private fun `generator for percentageOf() works as expected`(): Stream? { + return Stream.of( + Arguments.of( + 4.2, 42, Amount.TEN + ), + Arguments.of( + Amount.ZERO, 42, Amount.ZERO + ), + Arguments.of( + 42, Amount.ZERO, Amount.NOTHING + ), + Arguments.of( + Amount.NOTHING, 42, Amount.NOTHING + ), + Arguments.of( + 42, Amount.NOTHING, Amount.NOTHING + ), + ) + } + + @JvmStatic + private fun `generator for percentageDifferenceOf() works as expected`(): Stream? { + return Stream.of( + Arguments.of( + 4.62, 4.2, Amount.TEN + ), + Arguments.of( + Amount.ZERO, 42, -100 + ), + Arguments.of( + 42, Amount.ZERO, Amount.NOTHING + ), + Arguments.of( + Amount.NOTHING, 42, Amount.NOTHING + ), + Arguments.of( + 42, Amount.NOTHING, Amount.NOTHING + ), + ) + } + + @JvmStatic + private fun `generator for toPercent() works as expected`(): Stream? { + return Stream.of( + Arguments.of( + 0.42, 42 + ), + Arguments.of( + Amount.ONE, Amount.ONE_HUNDRED + ), + Arguments.of( + 2, 200 + ), + Arguments.of( + Amount.ZERO, Amount.ZERO + ), + Arguments.of( + Amount.NOTHING, Amount.NOTHING + ), + ) + } + + @JvmStatic + private fun `generator for asDecimal() works as expected`(): Stream? { + return Stream.of( + Arguments.of( + 42, 0.42 + ), + Arguments.of( + Amount.ONE_HUNDRED, Amount.ONE + ), + Arguments.of( + 200, 2 + ), + Arguments.of( + Amount.ZERO, Amount.ZERO + ), + Arguments.of( + Amount.NOTHING, Amount.NOTHING + ), + ) + } + + @JvmStatic + private fun `generator for remainder() works as expected`(): Stream? { + return Stream.of( + Arguments.of( + 10, 2, 0 + ), + Arguments.of( + 10, 3, 1 + ), + Arguments.of( + 10, 0, Amount.NOTHING + ), + Arguments.of( + 0, 10, 0 + ), + Arguments.of( + Amount.NOTHING, 10, Amount.NOTHING + ), + Arguments.of( + 10, Amount.NOTHING, Amount.NOTHING + ) + ) + } } - def "Amount.of converts various types correctly"() { - when: - CallContext.getCurrent().setLanguage("en") - then: - Amount.ONE == x - - where: - x << [Amount.of(1.0D), - Amount.of(1L), - Amount.of(Integer.valueOf(1)), - Amount.of(Long.valueOf(1L)), - Amount.of(Double.valueOf(1D)), - Amount.ofMachineString("1.0"), - Amount.ofUserString("1.0")] + + @Test + fun `predicates are evaluated correctly`() { + assertTrue { Amount.NOTHING.isEmpty } + assertTrue { Amount.NOTHING.isZeroOrNull } + assertTrue { Amount.ZERO.isZeroOrNull } + assertTrue { Amount.ZERO.isZero } + assertTrue { Amount.MINUS_ONE.isNonZero } + assertTrue { Amount.MINUS_ONE.isNegative } + assertTrue { Amount.TEN.isPositive } + assertTrue { Amount.TEN > Amount.ONE } } - def "ofMachineString works correctly"() { - expect: - Amount.ofMachineString(input) == output + @Test + fun `Amountof converts various types correctly`() { + CallContext.getCurrent().setLanguage("en") + assertEquals(Amount.ONE, Amount.of(1.0)) + assertEquals(Amount.ONE, Amount.of(1L)) + assertEquals(Amount.ONE, Amount.of(Integer.valueOf(1))) + assertEquals(Amount.ONE, Amount.of((1L).toLong())) + assertEquals(Amount.ONE, Amount.of((1.0).toDouble())) + assertEquals(Amount.ONE, Amount.ofMachineString("1.0")) + assertEquals(Amount.ONE, Amount.ofUserString("1.0")) + } - where: - input | output - "1" | Amount.ONE - "0.1" | Amount.ONE.divideBy(Amount.TEN) - "10" | Amount.TEN + @Test + fun `ofMachineString works correctly`() { + assertEquals(Amount.ONE, Amount.ofMachineString("1")) + assertEquals(Amount.ONE.divideBy(Amount.TEN), Amount.ofMachineString("0.1")) + assertEquals(Amount.TEN, Amount.ofMachineString("10")) } - def "Computations with NOTHING result in expected values"() { - expect: - Amount.ONE == Amount.ONE.add(Amount.NOTHING) - Amount.NOTHING == Amount.NOTHING.add(Amount.ONE) - Amount.NOTHING == Amount.NOTHING.add(Amount.NOTHING) - Amount.of(2) == Amount.ONE.add(Amount.ONE) - Amount.MINUS_ONE == Amount.ONE.subtract(Amount.of(2)) - Amount.ONE_HUNDRED == Amount.TEN.times(Amount.TEN) - Amount.NOTHING == Amount.TEN.times(Amount.NOTHING) - Amount.NOTHING == Amount.NOTHING.times(Amount.TEN) - Amount.NOTHING == Amount.ONE.divideBy(Amount.ZERO) - Amount.NOTHING == Amount.ONE.divideBy(Amount.NOTHING) - Amount.NOTHING == Amount.NOTHING.divideBy(Amount.ONE) - Amount.TEN == Amount.ONE_HUNDRED.divideBy(Amount.TEN) - Amount.of(2) == Amount.ONE.increasePercent(Amount.ONE_HUNDRED) - Amount.of(0.5) == Amount.ONE.decreasePercent(Amount.of(50)) + @Test + fun `Computations with NOTHING result in expected values`() { + assertEquals(Amount.ONE, Amount.ONE.add(Amount.NOTHING)) + assertEquals(Amount.NOTHING, Amount.NOTHING.add(Amount.ONE)) + assertEquals(Amount.NOTHING, Amount.NOTHING.add(Amount.NOTHING)) + assertEquals(Amount.of((2).toInt()), Amount.ONE.add(Amount.ONE)) + assertEquals(Amount.MINUS_ONE, Amount.ONE.subtract(Amount.of((2).toInt()))) + assertEquals(Amount.ONE_HUNDRED, Amount.TEN.times(Amount.TEN)) + assertEquals(Amount.NOTHING, Amount.TEN.times(Amount.NOTHING)) + assertEquals(Amount.NOTHING, Amount.NOTHING.times(Amount.TEN)) + assertEquals(Amount.NOTHING, Amount.ONE.divideBy(Amount.ZERO)) + assertEquals(Amount.NOTHING, Amount.ONE.divideBy(Amount.NOTHING)) + assertEquals(Amount.NOTHING, Amount.NOTHING.divideBy(Amount.ONE)) + assertEquals(Amount.TEN, Amount.ONE_HUNDRED.divideBy(Amount.TEN)) + assertEquals(Amount.of((2).toInt()), Amount.ONE.increasePercent(Amount.ONE_HUNDRED)) + assertEquals(Amount.of((0.5).toDouble()), Amount.ONE.decreasePercent(Amount.of((50).toInt()))) } - def "fill and orElseGet are only evaluated if no value is present"() { - expect: - Amount.TEN == Amount.NOTHING.fill(Amount.TEN) - Amount.TEN == Amount.TEN.fill(Amount.ONE) - Amount.NOTHING.orElseGet(new Supplier() { - @Override - Amount get() { - return Amount.TEN - } - }) == Amount.TEN + @Test + fun `fill and orElseGet are only evaluated if no value is present`() { + assertEquals(Amount.TEN, Amount.NOTHING.fill(Amount.TEN)) + assertEquals(Amount.TEN, Amount.TEN.fill(Amount.ONE)) + assertEquals(Amount.NOTHING.orElseGet { Amount.TEN }, Amount.TEN) } - def "helper functions compute correct values"() { - expect: - Amount.TEN == Amount.TEN.percentageOf(Amount.ONE_HUNDRED) - Amount.ONE_HUNDRED == Amount.TEN.percentageDifferenceOf(Amount.of(5)) - Amount.of(-50) == Amount.of(5).percentageDifferenceOf(Amount.TEN) - Amount.of(0.5) == Amount.of(50).asDecimal() - Amount.ONE_HUNDRED.getDigits() == 3 - Amount.ONE.getDigits() == 1 - Amount.ONE_HUNDRED.subtract(Amount.ONE).getDigits() == 2 - Amount.of(477).getDigits() == 3 + @Test + fun `helper functions compute correct values`() { + assertEquals(Amount.TEN, Amount.TEN.percentageOf(Amount.ONE_HUNDRED)) + assertEquals(Amount.ONE_HUNDRED, Amount.TEN.percentageDifferenceOf(Amount.of((5).toInt()))) + assertEquals(Amount.of((-50).toInt()), Amount.of((5).toInt()).percentageDifferenceOf(Amount.TEN)) + assertEquals(Amount.of((0.5).toDouble()), Amount.of((50).toInt()).asDecimal()) + assertEquals(3, Amount.ONE_HUNDRED.digits) + assertEquals(1, Amount.ONE.getDigits()) + assertEquals(2, Amount.ONE_HUNDRED.subtract(Amount.ONE).getDigits()) + assertEquals(3, Amount.of((477).toInt()).getDigits()) } - def "rounding works as expected"() { - expect: - "1.23" == Amount.ofMachineString("1.23223").round(2, RoundingMode.HALF_UP).toMachineString() - "1.232" == Amount.ofMachineString("1.23223").round(3, RoundingMode.HALF_UP).toMachineString() - "1.2" == Amount.ofMachineString("1.23223").round(1, RoundingMode.HALF_UP).toMachineString() + @Test + fun `rounding works as expected`() { + assertEquals("1.23", Amount.ofMachineString("1.23223").round(2, RoundingMode.HALF_UP).toMachineString()) + assertEquals("1.232", Amount.ofMachineString("1.23223").round(3, RoundingMode.HALF_UP).toMachineString()) + assertEquals("1.2", Amount.ofMachineString("1.23223").round(1, RoundingMode.HALF_UP).toMachineString()) } - def "formatting works as expected"() { - when: + @Test + fun `formatting works as expected`() { CallContext.getCurrent().setLanguage("en") - then: - Amount.ofMachineString("0.1").toPercent().toPercentString() == "10 %" - Amount.of(1.23).toRoundedString() == "1" - Amount.of(1.00).toSmartRoundedString(NumberFormat.TWO_DECIMAL_PLACES).asString() == "1" - Amount.of(1.00).toString(NumberFormat.TWO_DECIMAL_PLACES).asString() == "1.00" - Amount.of(1.23).toSmartRoundedString(NumberFormat.TWO_DECIMAL_PLACES).asString() == "1.23" - Amount.of(0.012).toScientificString(0, "") == "12 m" - Amount.of(1200).toScientificString(1, "") == "1.2 K" + assertEquals("10 %", Amount.ofMachineString("0.1").toPercent().toPercentString()) + assertEquals("1", Amount.of(1.23).toRoundedString()) + assertEquals("1", Amount.of(1.00).toSmartRoundedString(NumberFormat.TWO_DECIMAL_PLACES).asString()) + assertEquals("1.00", Amount.of(1.00).toString(NumberFormat.TWO_DECIMAL_PLACES).asString()) + assertEquals("1.23", Amount.of(1.23).toSmartRoundedString(NumberFormat.TWO_DECIMAL_PLACES).asString()) + assertEquals("12 m", Amount.of(0.012).toScientificString(0, "")) + assertEquals("1.2 K", Amount.of((1200).toInt()).toScientificString(1, "")) } - def "min selects the lower value and handles null and NOTHING gracefully"() { - expect: - Amount.ONE.min(Amount.TEN) == Amount.ONE - Amount.TEN.min(Amount.ONE) == Amount.ONE - Amount.NOTHING.min(Amount.ONE) == Amount.ONE - Amount.ONE.min(Amount.NOTHING) == Amount.ONE - Amount.NOTHING.min(Amount.NOTHING) == Amount.NOTHING - Amount.TEN.min(null) == Amount.TEN - Amount.ZERO.min(Amount.NOTHING) == Amount.ZERO + @Test + fun `min selects the lower value and handles null and NOTHING gracefully`() { + assertEquals(Amount.ONE, Amount.ONE.min(Amount.TEN)) + assertEquals(Amount.ONE, Amount.TEN.min(Amount.ONE)) + assertEquals(Amount.ONE, Amount.NOTHING.min(Amount.ONE)) + assertEquals(Amount.ONE, Amount.ONE.min(Amount.NOTHING)) + assertEquals(Amount.NOTHING, Amount.NOTHING.min(Amount.NOTHING)) + assertEquals(Amount.TEN, Amount.TEN.min(null)) + assertEquals(Amount.ZERO, Amount.ZERO.min(Amount.NOTHING)) } - def "max selects the higher value and handles null and NOTHING gracefully"() { - expect: - Amount.ONE.max(Amount.TEN) == Amount.TEN - Amount.TEN.max(Amount.ONE) == Amount.TEN - Amount.NOTHING.max(Amount.ONE) == Amount.ONE - Amount.ONE.max(Amount.NOTHING) == Amount.ONE - Amount.NOTHING.max(Amount.NOTHING) == Amount.NOTHING - Amount.TEN.max(null) == Amount.TEN - Amount.ZERO.max(Amount.NOTHING) == Amount.ZERO + @Test + fun `max selects the higher value and handles null and NOTHING gracefully`() { + assertEquals(Amount.TEN, Amount.ONE.max(Amount.TEN)) + assertEquals(Amount.TEN, Amount.TEN.max(Amount.ONE)) + assertEquals(Amount.ONE, Amount.NOTHING.max(Amount.ONE)) + assertEquals(Amount.ONE, Amount.ONE.max(Amount.NOTHING)) + assertEquals(Amount.NOTHING, Amount.NOTHING.max(Amount.NOTHING)) + assertEquals(Amount.TEN, Amount.TEN.max(null)) + assertEquals(Amount.ZERO, Amount.ZERO.max(Amount.NOTHING)) } - def "compare returns which amount is higher"() { - expect: - Amount.NOTHING.compareTo(Amount.NOTHING) == 0 - Amount.ONE.compareTo(Amount.NOTHING) > 0 - Amount.NOTHING.compareTo(Amount.ONE) < 0 - Amount.ONE.compareTo(Amount.ONE) == 0 - Amount.ONE.compareTo(Amount.MINUS_ONE) > 0 - Amount.MINUS_ONE.compareTo(Amount.ONE) < 0 + @Test + fun `compare returns which amount is higher`() { + assertEquals(0, Amount.NOTHING.compareTo(Amount.NOTHING)) + assertTrue { Amount.ONE.compareTo(Amount.NOTHING) > 0 } + assertTrue { Amount.NOTHING.compareTo(Amount.ONE) < 0 } + assertEquals(0, Amount.ONE.compareTo(Amount.ONE)) + assertTrue { Amount.ONE.compareTo(Amount.MINUS_ONE) > 0 } + assertTrue { Amount.MINUS_ONE.compareTo(Amount.ONE) < 0 } } - def "boilerplace comparators work"() { - expect: - Amount.ONE.isGreaterThan(Amount.NOTHING) - Amount.ONE.isGreaterThanOrEqual(Amount.NOTHING) - Amount.NOTHING.isLessThan(Amount.ONE) - Amount.NOTHING.isLessThanOrEqual(Amount.ONE) - Amount.ONE.isGreaterThanOrEqual(Amount.ONE) - Amount.ONE.isLessThanOrEqual(Amount.ONE) - Amount.ONE.isGreaterThan(Amount.MINUS_ONE) - Amount.MINUS_ONE.isLessThan(Amount.ONE) + @Test + fun `boilerplace comparators work`() { + assertTrue { Amount.ONE.isGreaterThan(Amount.NOTHING) } + assertTrue { Amount.ONE.isGreaterThanOrEqual(Amount.NOTHING) } + assertTrue { Amount.NOTHING.isLessThan(Amount.ONE) } + assertTrue { Amount.NOTHING.isLessThanOrEqual(Amount.ONE) } + assertTrue { Amount.ONE.isGreaterThanOrEqual(Amount.ONE) } + assertTrue { Amount.ONE.isLessThanOrEqual(Amount.ONE) } + assertTrue { Amount.ONE.isGreaterThan(Amount.MINUS_ONE) } + assertTrue { Amount.MINUS_ONE.isLessThan(Amount.ONE) } } - def "add() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountB = b instanceof Amount ? b : Amount.of(b) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.add(amountB) == amountResult - where: - a | b | result - 4.2 | 42 | 46.2 - 42 | 4.2 | 46.2 - Amount.ZERO | 42 | 42 - 42 | Amount.ZERO | 42 - Amount.NOTHING | 42 | Amount.NOTHING - 42 | Amount.NOTHING | 42 + @ParameterizedTest + @MethodSource("generator for add() works as expected") + fun `add() works as expected`( + a: Number, + b: Number, + result: Number, + ) { + val amountA = if (a is Amount) a else Amount.of(a.toDouble()) + val amountB = if (b is Amount) b else Amount.of(b.toDouble()) + val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) + assertEquals(amountResult, amountA.add(amountB)) } - def "subtract() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountB = b instanceof Amount ? b : Amount.of(b) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.subtract(amountB) == amountResult - where: - a | b | result - 4.2 | 42 | -37.8 - 42 | 4.2 | 37.8 - Amount.ZERO | 42 | -42 - 42 | Amount.ZERO | 42 - Amount.NOTHING | 42 | Amount.NOTHING - 42 | Amount.NOTHING | 42 + @ParameterizedTest + @MethodSource("generator for subtract() works as expected") + fun `subtract() works as expected`( + a: Number, + b: Number, + result: Number, + ) { + val amountA = if (a is Amount) a else Amount.of(a.toDouble()) + val amountB = if (b is Amount) b else Amount.of(b.toDouble()) + val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) + assertEquals(amountResult, amountA.subtract(amountB)) } - def "times() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountB = b instanceof Amount ? b : Amount.of(b) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.times(amountB) == amountResult - where: - a | b | result - 4.2 | 42 | 176.4 - 42 | 4.2 | 176.4 - Amount.ZERO | 42 | Amount.ZERO - 42 | Amount.ZERO | Amount.ZERO - Amount.NOTHING | 42 | Amount.NOTHING - 42 | Amount.NOTHING | Amount.NOTHING + @ParameterizedTest + @MethodSource("generator for times() works as expected") + fun `times() works as expected`( + a: Number, + b: Number, + result: Number, + ) { + val amountA = if (a is Amount) a else Amount.of(a.toDouble()) + val amountB = if (b is Amount) b else Amount.of(b.toDouble()) + val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) + assertEquals(amountResult, amountA.times(amountB)) } - def "divideBy() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountB = b instanceof Amount ? b : Amount.of(b) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.divideBy(amountB) == amountResult - where: - a | b | result - 4.2 | 42 | 0.1 - 42 | 4.2 | Amount.TEN - Amount.ZERO | 42 | Amount.ZERO - 42 | Amount.ZERO | Amount.NOTHING - Amount.NOTHING | 42 | Amount.NOTHING - 42 | Amount.NOTHING | Amount.NOTHING + @ParameterizedTest + @MethodSource("generator for divideBy() works as expected") + fun `divideBy() works as expected`( + a: Number, + b: Number, + result: Number, + ) { + val amountA = if (a is Amount) a else Amount.of(a.toDouble()) + val amountB = if (b is Amount) b else Amount.of(b.toDouble()) + val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) + assertEquals(amountResult, amountA.divideBy(amountB)) } - def "negate() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.negate() == amountResult - where: - a | result - 4.2 | -4.2 - -4.2 | 4.2 - 42 | -42 - Amount.ZERO | Amount.ZERO - -0 | Amount.ZERO - Amount.NOTHING | Amount.NOTHING + @ParameterizedTest + @MethodSource("generator for negate() works as expected") + fun `negate() works as expected`( + a: Number, + result: Number, + ) { + val amountA = if (a is Amount) a else Amount.of(a.toDouble()) + val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) + assertEquals(amountResult, amountA.negate()) } - def "increasePercent() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountB = b instanceof Amount ? b : Amount.of(b) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.increasePercent(amountB) == amountResult - where: - a | b | result - 4.2 | Amount.TEN | 4.62 - Amount.ZERO | 42 | Amount.ZERO - 42 | Amount.ZERO | 42 - Amount.NOTHING | 42 | Amount.NOTHING - 42 | Amount.NOTHING | 42 + @ParameterizedTest + @MethodSource("generator for increasePercent() works as expected") + fun `increasePercent() works as expected`( + a: Number, + b: Number, + result: Number, + ) { + val amountA = if (a is Amount) a else Amount.of(a.toDouble()) + val amountB = if (b is Amount) b else Amount.of(b.toDouble()) + val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) + assertEquals(amountResult, amountA.increasePercent(amountB)) } - def "decreasePercent() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountB = b instanceof Amount ? b : Amount.of(b) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.decreasePercent(amountB) == amountResult - where: - a | b | result - 4.2 | Amount.TEN | 3.78 - Amount.ZERO | 42 | Amount.ZERO - 42 | Amount.ZERO | 42 - Amount.NOTHING | 42 | Amount.NOTHING - 42 | Amount.NOTHING | 42 + @ParameterizedTest + @MethodSource("generator for decreasePercent() works as expected") + fun `decreasePercent() works as expected`( + a: Number, + b: Number, + result: Number, + ) { + val amountA = if (a is Amount) a else Amount.of(a.toDouble()) + val amountB = if (b is Amount) b else Amount.of(b.toDouble()) + val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) + assertEquals(amountResult, amountA.decreasePercent(amountB)) } - def "percentageOf() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountB = b instanceof Amount ? b : Amount.of(b) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.percentageOf(amountB) == amountResult - where: - a | b | result - 4.2 | 42 | Amount.TEN - Amount.ZERO | 42 | Amount.ZERO - 42 | Amount.ZERO | Amount.NOTHING - Amount.NOTHING | 42 | Amount.NOTHING - 42 | Amount.NOTHING | Amount.NOTHING + @ParameterizedTest + @MethodSource("generator for percentageOf() works as expected") + fun `percentageOf() works as expected`( + a: Number, + b: Number, + result: Number, + ) { + val amountA = if (a is Amount) a else Amount.of(a.toDouble()) + val amountB = if (b is Amount) b else Amount.of(b.toDouble()) + val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) + assertEquals(amountResult, amountA.percentageOf(amountB)) } - def "percentageDifferenceOf() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountB = b instanceof Amount ? b : Amount.of(b) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.percentageDifferenceOf(amountB) == amountResult - where: - a | b | result - 4.62 | 4.2 | Amount.TEN - Amount.ZERO | 42 | -100 - 42 | Amount.ZERO | Amount.NOTHING - Amount.NOTHING | 42 | Amount.NOTHING - 42 | Amount.NOTHING | Amount.NOTHING + @ParameterizedTest + @MethodSource("generator for percentageDifferenceOf() works as expected") + fun `percentageDifferenceOf() works as expected`( + a: Number, + b: Number, + result: Number, + ) { + val amountA = if (a is Amount) a else Amount.of(a.toDouble()) + val amountB = if (b is Amount) b else Amount.of(b.toDouble()) + val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) + assertEquals(amountResult, amountA.percentageDifferenceOf(amountB)) } - def "toPercent() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.toPercent() == amountResult - where: - a | result - 0.42 | 42 - Amount.ONE | Amount.ONE_HUNDRED - 2 | 200 - Amount.ZERO | Amount.ZERO - Amount.NOTHING | Amount.NOTHING + @ParameterizedTest + @MethodSource("generator for toPercent() works as expected") + fun `toPercent() works as expected`( + a: Number, + result: Number, + ) { + val amountA = if (a is Amount) a else Amount.of(a.toDouble()) + val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) + assertEquals(amountResult, amountA.toPercent()) + } - def "asDecimal() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.asDecimal() == amountResult - where: - a | result - 42 | 0.42 - Amount.ONE_HUNDRED | Amount.ONE - 200 | 2 - Amount.ZERO | Amount.ZERO - Amount.NOTHING | Amount.NOTHING + @ParameterizedTest + @MethodSource("generator for asDecimal() works as expected") + fun `asDecimal() works as expected`( + a: Number, + result: Number, + ) { + val amountA = if (a is Amount) a else Amount.of(a.toDouble()) + val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) + assertEquals(amountResult, amountA.asDecimal()) } - def "remainder() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountB = b instanceof Amount ? b : Amount.of(b) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.remainder(amountB) == amountResult - where: - a | b | result - 10 | 2 | 0 - 10 | 3 | 1 - 10 | 0 | Amount.NOTHING - 0 | 10 | 0 - Amount.NOTHING | 10 | Amount.NOTHING - 10 | Amount.NOTHING | Amount.NOTHING + @ParameterizedTest + @MethodSource("generator for remainder() works as expected") + fun `remainder() works as expected`( + a: Number, + b: Number, + result: Number, + ) { + val amountA = if (a is Amount) a else Amount.of(a.toDouble()) + val amountB = if (b is Amount) b else Amount.of(b.toDouble()) + val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) + assertEquals(amountResult, amountA.remainder(amountB)) } } From 17a880e734f30377935f6c2b02438438b58b53c8 Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Tue, 5 Sep 2023 16:23:38 +0200 Subject: [PATCH 066/111] deleted AmountSpec.groovy --- .../sirius/kernel/commons/AmountSpec.groovy | 350 ------------------ 1 file changed, 350 deletions(-) delete mode 100644 src/test/java/sirius/kernel/commons/AmountSpec.groovy diff --git a/src/test/java/sirius/kernel/commons/AmountSpec.groovy b/src/test/java/sirius/kernel/commons/AmountSpec.groovy deleted file mode 100644 index a1360097..00000000 --- a/src/test/java/sirius/kernel/commons/AmountSpec.groovy +++ /dev/null @@ -1,350 +0,0 @@ -package sirius.kernel.commons - -import sirius.kernel.BaseSpecification -import sirius.kernel.async.CallContext - -import java.math.RoundingMode -import java.util.function.Supplier - -class AmountSpec extends BaseSpecification { - - def "predicates are evaluated correctly"() { - expect: - Amount.NOTHING.isEmpty() - Amount.NOTHING.isZeroOrNull() - Amount.ZERO.isZeroOrNull() - Amount.ZERO.isZero() - Amount.MINUS_ONE.isNonZero() - Amount.MINUS_ONE.isNegative() - Amount.TEN.isPositive() - Amount.TEN > Amount.ONE - } - - def "Amount.of converts various types correctly"() { - when: - CallContext.getCurrent().setLanguage("en") - then: - Amount.ONE == x - - where: - x << [Amount.of(1.0D), - Amount.of(1L), - Amount.of(Integer.valueOf(1)), - Amount.of(Long.valueOf(1L)), - Amount.of(Double.valueOf(1D)), - Amount.ofMachineString("1.0"), - Amount.ofUserString("1.0")] - } - - def "ofMachineString works correctly"() { - expect: - Amount.ofMachineString(input) == output - - where: - input | output - "1" | Amount.ONE - "0.1" | Amount.ONE.divideBy(Amount.TEN) - "10" | Amount.TEN - } - - def "Computations with NOTHING result in expected values"() { - expect: - Amount.ONE == Amount.ONE.add(Amount.NOTHING) - Amount.NOTHING == Amount.NOTHING.add(Amount.ONE) - Amount.NOTHING == Amount.NOTHING.add(Amount.NOTHING) - Amount.of(2) == Amount.ONE.add(Amount.ONE) - Amount.MINUS_ONE == Amount.ONE.subtract(Amount.of(2)) - Amount.ONE_HUNDRED == Amount.TEN.times(Amount.TEN) - Amount.NOTHING == Amount.TEN.times(Amount.NOTHING) - Amount.NOTHING == Amount.NOTHING.times(Amount.TEN) - Amount.NOTHING == Amount.ONE.divideBy(Amount.ZERO) - Amount.NOTHING == Amount.ONE.divideBy(Amount.NOTHING) - Amount.NOTHING == Amount.NOTHING.divideBy(Amount.ONE) - Amount.TEN == Amount.ONE_HUNDRED.divideBy(Amount.TEN) - Amount.of(2) == Amount.ONE.increasePercent(Amount.ONE_HUNDRED) - Amount.of(0.5) == Amount.ONE.decreasePercent(Amount.of(50)) - } - - def "fill and orElseGet are only evaluated if no value is present"() { - expect: - Amount.TEN == Amount.NOTHING.fill(Amount.TEN) - Amount.TEN == Amount.TEN.fill(Amount.ONE) - Amount.NOTHING.orElseGet(new Supplier() { - @Override - Amount get() { - return Amount.TEN - } - }) == Amount.TEN - } - - def "helper functions compute correct values"() { - expect: - Amount.TEN == Amount.TEN.percentageOf(Amount.ONE_HUNDRED) - Amount.ONE_HUNDRED == Amount.TEN.percentageDifferenceOf(Amount.of(5)) - Amount.of(-50) == Amount.of(5).percentageDifferenceOf(Amount.TEN) - Amount.of(0.5) == Amount.of(50).asDecimal() - Amount.ONE_HUNDRED.getDigits() == 3 - Amount.ONE.getDigits() == 1 - Amount.ONE_HUNDRED.subtract(Amount.ONE).getDigits() == 2 - Amount.of(477).getDigits() == 3 - } - - def "rounding works as expected"() { - expect: - "1.23" == Amount.ofMachineString("1.23223").round(2, RoundingMode.HALF_UP).toMachineString() - "1.232" == Amount.ofMachineString("1.23223").round(3, RoundingMode.HALF_UP).toMachineString() - "1.2" == Amount.ofMachineString("1.23223").round(1, RoundingMode.HALF_UP).toMachineString() - } - - def "formatting works as expected"() { - when: - CallContext.getCurrent().setLanguage("en") - then: - Amount.ofMachineString("0.1").toPercent().toPercentString() == "10 %" - Amount.of(1.23).toRoundedString() == "1" - Amount.of(1.00).toSmartRoundedString(NumberFormat.TWO_DECIMAL_PLACES).asString() == "1" - Amount.of(1.00).toString(NumberFormat.TWO_DECIMAL_PLACES).asString() == "1.00" - Amount.of(1.23).toSmartRoundedString(NumberFormat.TWO_DECIMAL_PLACES).asString() == "1.23" - Amount.of(0.012).toScientificString(0, "") == "12 m" - Amount.of(1200).toScientificString(1, "") == "1.2 K" - } - - def "min selects the lower value and handles null and NOTHING gracefully"() { - expect: - Amount.ONE.min(Amount.TEN) == Amount.ONE - Amount.TEN.min(Amount.ONE) == Amount.ONE - Amount.NOTHING.min(Amount.ONE) == Amount.ONE - Amount.ONE.min(Amount.NOTHING) == Amount.ONE - Amount.NOTHING.min(Amount.NOTHING) == Amount.NOTHING - Amount.TEN.min(null) == Amount.TEN - Amount.ZERO.min(Amount.NOTHING) == Amount.ZERO - } - - def "max selects the higher value and handles null and NOTHING gracefully"() { - expect: - Amount.ONE.max(Amount.TEN) == Amount.TEN - Amount.TEN.max(Amount.ONE) == Amount.TEN - Amount.NOTHING.max(Amount.ONE) == Amount.ONE - Amount.ONE.max(Amount.NOTHING) == Amount.ONE - Amount.NOTHING.max(Amount.NOTHING) == Amount.NOTHING - Amount.TEN.max(null) == Amount.TEN - Amount.ZERO.max(Amount.NOTHING) == Amount.ZERO - } - - def "compare returns which amount is higher"() { - expect: - Amount.NOTHING.compareTo(Amount.NOTHING) == 0 - Amount.ONE.compareTo(Amount.NOTHING) > 0 - Amount.NOTHING.compareTo(Amount.ONE) < 0 - Amount.ONE.compareTo(Amount.ONE) == 0 - Amount.ONE.compareTo(Amount.MINUS_ONE) > 0 - Amount.MINUS_ONE.compareTo(Amount.ONE) < 0 - } - - def "boilerplace comparators work"() { - expect: - Amount.ONE.isGreaterThan(Amount.NOTHING) - Amount.ONE.isGreaterThanOrEqual(Amount.NOTHING) - Amount.NOTHING.isLessThan(Amount.ONE) - Amount.NOTHING.isLessThanOrEqual(Amount.ONE) - Amount.ONE.isGreaterThanOrEqual(Amount.ONE) - Amount.ONE.isLessThanOrEqual(Amount.ONE) - Amount.ONE.isGreaterThan(Amount.MINUS_ONE) - Amount.MINUS_ONE.isLessThan(Amount.ONE) - } - - def "add() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountB = b instanceof Amount ? b : Amount.of(b) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.add(amountB) == amountResult - where: - a | b | result - 4.2 | 42 | 46.2 - 42 | 4.2 | 46.2 - Amount.ZERO | 42 | 42 - 42 | Amount.ZERO | 42 - Amount.NOTHING | 42 | Amount.NOTHING - 42 | Amount.NOTHING | 42 - } - - def "subtract() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountB = b instanceof Amount ? b : Amount.of(b) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.subtract(amountB) == amountResult - where: - a | b | result - 4.2 | 42 | -37.8 - 42 | 4.2 | 37.8 - Amount.ZERO | 42 | -42 - 42 | Amount.ZERO | 42 - Amount.NOTHING | 42 | Amount.NOTHING - 42 | Amount.NOTHING | 42 - } - - def "times() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountB = b instanceof Amount ? b : Amount.of(b) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.times(amountB) == amountResult - where: - a | b | result - 4.2 | 42 | 176.4 - 42 | 4.2 | 176.4 - Amount.ZERO | 42 | Amount.ZERO - 42 | Amount.ZERO | Amount.ZERO - Amount.NOTHING | 42 | Amount.NOTHING - 42 | Amount.NOTHING | Amount.NOTHING - } - - def "divideBy() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountB = b instanceof Amount ? b : Amount.of(b) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.divideBy(amountB) == amountResult - where: - a | b | result - 4.2 | 42 | 0.1 - 42 | 4.2 | Amount.TEN - Amount.ZERO | 42 | Amount.ZERO - 42 | Amount.ZERO | Amount.NOTHING - Amount.NOTHING | 42 | Amount.NOTHING - 42 | Amount.NOTHING | Amount.NOTHING - } - - def "negate() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.negate() == amountResult - where: - a | result - 4.2 | -4.2 - -4.2 | 4.2 - 42 | -42 - Amount.ZERO | Amount.ZERO - -0 | Amount.ZERO - Amount.NOTHING | Amount.NOTHING - } - - def "increasePercent() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountB = b instanceof Amount ? b : Amount.of(b) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.increasePercent(amountB) == amountResult - where: - a | b | result - 4.2 | Amount.TEN | 4.62 - Amount.ZERO | 42 | Amount.ZERO - 42 | Amount.ZERO | 42 - Amount.NOTHING | 42 | Amount.NOTHING - 42 | Amount.NOTHING | 42 - } - - def "decreasePercent() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountB = b instanceof Amount ? b : Amount.of(b) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.decreasePercent(amountB) == amountResult - where: - a | b | result - 4.2 | Amount.TEN | 3.78 - Amount.ZERO | 42 | Amount.ZERO - 42 | Amount.ZERO | 42 - Amount.NOTHING | 42 | Amount.NOTHING - 42 | Amount.NOTHING | 42 - } - - def "percentageOf() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountB = b instanceof Amount ? b : Amount.of(b) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.percentageOf(amountB) == amountResult - where: - a | b | result - 4.2 | 42 | Amount.TEN - Amount.ZERO | 42 | Amount.ZERO - 42 | Amount.ZERO | Amount.NOTHING - Amount.NOTHING | 42 | Amount.NOTHING - 42 | Amount.NOTHING | Amount.NOTHING - } - - def "percentageDifferenceOf() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountB = b instanceof Amount ? b : Amount.of(b) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.percentageDifferenceOf(amountB) == amountResult - where: - a | b | result - 4.62 | 4.2 | Amount.TEN - Amount.ZERO | 42 | -100 - 42 | Amount.ZERO | Amount.NOTHING - Amount.NOTHING | 42 | Amount.NOTHING - 42 | Amount.NOTHING | Amount.NOTHING - } - - def "toPercent() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.toPercent() == amountResult - where: - a | result - 0.42 | 42 - Amount.ONE | Amount.ONE_HUNDRED - 2 | 200 - Amount.ZERO | Amount.ZERO - Amount.NOTHING | Amount.NOTHING - } - - def "asDecimal() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.asDecimal() == amountResult - where: - a | result - 42 | 0.42 - Amount.ONE_HUNDRED | Amount.ONE - 200 | 2 - Amount.ZERO | Amount.ZERO - Amount.NOTHING | Amount.NOTHING - } - - def "remainder() works as expected"() { - given: - Amount amountA = a instanceof Amount ? a : Amount.of(a) - Amount amountB = b instanceof Amount ? b : Amount.of(b) - Amount amountResult = result instanceof Amount ? result : Amount.of(result) - expect: - amountA.remainder(amountB) == amountResult - where: - a | b | result - 10 | 2 | 0 - 10 | 3 | 1 - 10 | 0 | Amount.NOTHING - 0 | 10 | 0 - Amount.NOTHING | 10 | Amount.NOTHING - 10 | Amount.NOTHING | Amount.NOTHING - } -} From db5b444c4bbf25244a955f3ff62faf85a5b3ad9c Mon Sep 17 00:00:00 2001 From: mko Date: Wed, 6 Sep 2023 15:09:44 +0200 Subject: [PATCH 067/111] Fixes a typo in the javadoc for MultiMap#put --- src/main/java/sirius/kernel/commons/MultiMap.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/sirius/kernel/commons/MultiMap.java b/src/main/java/sirius/kernel/commons/MultiMap.java index aeae4c6c..a83636e0 100644 --- a/src/main/java/sirius/kernel/commons/MultiMap.java +++ b/src/main/java/sirius/kernel/commons/MultiMap.java @@ -96,7 +96,7 @@ protected List createValueList() { /** * Adds the given value to the list of values kept for the given key. *

- * Note that the values for a given key don't from a Set. Therefore, adding the same value twice + * Note that the values for a given key don't form a Set. Therefore, adding the same value twice * for the same key, will result in having a value list containing the added element twice. * * @param key the key for which the value is added to the map From 72fdff1a3604b1c5537092972fe3e19e903c1bfc Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Wed, 6 Sep 2023 15:27:45 +0200 Subject: [PATCH 068/111] refactored tests - now without using method source --- .../sirius/kernel/commons/AmountTest.kt | 530 ++++++------------ 1 file changed, 170 insertions(+), 360 deletions(-) diff --git a/src/test/kotlin/sirius/kernel/commons/AmountTest.kt b/src/test/kotlin/sirius/kernel/commons/AmountTest.kt index 5c584b5e..df6b181d 100644 --- a/src/test/kotlin/sirius/kernel/commons/AmountTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/AmountTest.kt @@ -10,13 +10,14 @@ package sirius.kernel.commons import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.ParameterContext import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.MethodSource +import org.junit.jupiter.params.converter.ArgumentConverter +import org.junit.jupiter.params.converter.ConvertWith +import org.junit.jupiter.params.provider.CsvSource import sirius.kernel.SiriusExtension import sirius.kernel.async.CallContext import java.math.RoundingMode -import java.util.stream.Stream import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -24,279 +25,21 @@ import kotlin.test.assertTrue /** * Tests the [Amount] class. */ -@ExtendWith(SiriusExtension::class) -class AmountTest { - - companion object { - @JvmStatic - private fun `generator for add() works as expected`(): Stream? { - return Stream.of( - Arguments.of( - 4.2, 42, 46.2 - ), - Arguments.of( - 42, 4.2, 46.2 - ), - Arguments.of( - Amount.ZERO, 42, 42 - ), - Arguments.of( - 42, Amount.ZERO, 42 - ), - Arguments.of( - Amount.NOTHING, 42, Amount.NOTHING - ), - Arguments.of( - 42, Amount.NOTHING, 42 - ), - ) - } - - @JvmStatic - private fun `generator for subtract() works as expected`(): Stream? { - return Stream.of( - Arguments.of( - 4.2, 42, -37.8 - ), - Arguments.of( - 42, 4.2, 37.8 - ), - Arguments.of( - Amount.ZERO, 42, -42 - ), - Arguments.of( - 42, Amount.ZERO, 42 - ), - Arguments.of( - Amount.NOTHING, 42, Amount.NOTHING - ), - Arguments.of( - 42, Amount.NOTHING, 42 - ), - ) - } - - @JvmStatic - private fun `generator for times() works as expected`(): Stream? { - return Stream.of( - Arguments.of( - 4.2, 42, 176.4 - ), - Arguments.of( - 42, 4.2, 176.4 - ), - Arguments.of(Amount.ZERO, 42, Amount.ZERO), - Arguments.of( - 42, Amount.ZERO, Amount.ZERO - ), - Arguments.of( - Amount.NOTHING, 42, Amount.NOTHING - ), - Arguments.of( - 42, Amount.NOTHING, Amount.NOTHING - ), - ) - } - - @JvmStatic - private fun `generator for divideBy() works as expected`(): Stream? { - return Stream.of( - Arguments.of( - 4.2, 42, 0.1 - ), - Arguments.of( - 42, 4.2, Amount.TEN - ), - Arguments.of( - Amount.ZERO, 42, Amount.ZERO - ), - Arguments.of( - 42, Amount.ZERO, Amount.NOTHING - ), - Arguments.of( - Amount.NOTHING, 42, Amount.NOTHING - ), - Arguments.of( - 42, Amount.NOTHING, Amount.NOTHING - ), - ) - } - - @JvmStatic - private fun `generator for negate() works as expected`(): Stream? { - return Stream.of( - Arguments.of( - 4.2, -4.2 - ), - Arguments.of( - -4.2, 4.2 - ), - Arguments.of( - 42, -42 - ), - Arguments.of( - Amount.ZERO, Amount.ZERO - ), - Arguments.of( - -0, Amount.ZERO - ), - Arguments.of( - Amount.NOTHING, Amount.NOTHING - ) - ) - } - - @JvmStatic - private fun `generator for increasePercent() works as expected`(): Stream? { - return Stream.of( - Arguments.of( - 4.2, Amount.TEN, 4.62 - ), - Arguments.of( - Amount.ZERO, 42, Amount.ZERO - ), - Arguments.of( - 42, Amount.ZERO, 42 - ), - Arguments.of( - Amount.NOTHING, 42, Amount.NOTHING - ), - Arguments.of( - 42, Amount.NOTHING, 42 - ), - ) - } - - @JvmStatic - private fun `generator for decreasePercent() works as expected`(): Stream? { - return Stream.of( - Arguments.of( - 4.2, Amount.TEN, 3.78 - ), - Arguments.of( - Amount.ZERO, 42, Amount.ZERO - ), - Arguments.of( - 42, Amount.ZERO, 42 - ), - Arguments.of( - Amount.NOTHING, 42, Amount.NOTHING - ), - Arguments.of( - 42, Amount.NOTHING, 42 - ), - ) - } - - @JvmStatic - private fun `generator for percentageOf() works as expected`(): Stream? { - return Stream.of( - Arguments.of( - 4.2, 42, Amount.TEN - ), - Arguments.of( - Amount.ZERO, 42, Amount.ZERO - ), - Arguments.of( - 42, Amount.ZERO, Amount.NOTHING - ), - Arguments.of( - Amount.NOTHING, 42, Amount.NOTHING - ), - Arguments.of( - 42, Amount.NOTHING, Amount.NOTHING - ), - ) - } - @JvmStatic - private fun `generator for percentageDifferenceOf() works as expected`(): Stream? { - return Stream.of( - Arguments.of( - 4.62, 4.2, Amount.TEN - ), - Arguments.of( - Amount.ZERO, 42, -100 - ), - Arguments.of( - 42, Amount.ZERO, Amount.NOTHING - ), - Arguments.of( - Amount.NOTHING, 42, Amount.NOTHING - ), - Arguments.of( - 42, Amount.NOTHING, Amount.NOTHING - ), - ) - } +class AmountConverter : ArgumentConverter { + override fun convert(source: Any?, context: ParameterContext?): Any? { + if (source !is String) { - @JvmStatic - private fun `generator for toPercent() works as expected`(): Stream? { - return Stream.of( - Arguments.of( - 0.42, 42 - ), - Arguments.of( - Amount.ONE, Amount.ONE_HUNDRED - ), - Arguments.of( - 2, 200 - ), - Arguments.of( - Amount.ZERO, Amount.ZERO - ), - Arguments.of( - Amount.NOTHING, Amount.NOTHING - ), - ) - } + return Amount.NOTHING - @JvmStatic - private fun `generator for asDecimal() works as expected`(): Stream? { - return Stream.of( - Arguments.of( - 42, 0.42 - ), - Arguments.of( - Amount.ONE_HUNDRED, Amount.ONE - ), - Arguments.of( - 200, 2 - ), - Arguments.of( - Amount.ZERO, Amount.ZERO - ), - Arguments.of( - Amount.NOTHING, Amount.NOTHING - ), - ) - } - - @JvmStatic - private fun `generator for remainder() works as expected`(): Stream? { - return Stream.of( - Arguments.of( - 10, 2, 0 - ), - Arguments.of( - 10, 3, 1 - ), - Arguments.of( - 10, 0, Amount.NOTHING - ), - Arguments.of( - 0, 10, 0 - ), - Arguments.of( - Amount.NOTHING, 10, Amount.NOTHING - ), - Arguments.of( - 10, Amount.NOTHING, Amount.NOTHING - ) - ) } + return Amount.ofMachineString(source) } +} + +@ExtendWith(SiriusExtension::class) +class AmountTest { @Test fun `predicates are evaluated correctly`() { @@ -429,154 +172,221 @@ class AmountTest { assertTrue { Amount.MINUS_ONE.isLessThan(Amount.ONE) } } + @ParameterizedTest - @MethodSource("generator for add() works as expected") + @CsvSource( + delimiter = '|', textBlock = """ + 4.2 | 42 | 46.2 + 42 | 4.2 | 46.2 + 0 | 42 | 42 + 42 | 0 | 42 + | 42 | + 42 | | 42 """ + ) fun `add() works as expected`( - a: Number, - b: Number, - result: Number, + @ConvertWith(AmountConverter::class) a: Amount, + @ConvertWith(AmountConverter::class) b: Amount, + @ConvertWith(AmountConverter::class) result: Amount, ) { - val amountA = if (a is Amount) a else Amount.of(a.toDouble()) - val amountB = if (b is Amount) b else Amount.of(b.toDouble()) - val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) - assertEquals(amountResult, amountA.add(amountB)) + + assertEquals(result, a.add(b)) } @ParameterizedTest - @MethodSource("generator for subtract() works as expected") + @CsvSource( + delimiter = '|', textBlock = """ + 4.2 | 42 | -37.8 + 42 | 4.2 | 37.8 + 0 | 42 | -42 + 42 | 0 | 42 + | 42 | + 42 | | 42 """ + ) fun `subtract() works as expected`( - a: Number, - b: Number, - result: Number, + @ConvertWith(AmountConverter::class) a: Amount, + @ConvertWith(AmountConverter::class) b: Amount, + @ConvertWith(AmountConverter::class) result: Amount, ) { - val amountA = if (a is Amount) a else Amount.of(a.toDouble()) - val amountB = if (b is Amount) b else Amount.of(b.toDouble()) - val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) - assertEquals(amountResult, amountA.subtract(amountB)) + + assertEquals(result, a.subtract(b)) } @ParameterizedTest - @MethodSource("generator for times() works as expected") + @CsvSource( + delimiter = '|', textBlock = """ + 4.2 | 42 | 176.4 + 42 | 4.2 | 176.4 + 0 | 42 | 0 + 42 | 0 | 0 + | 42 | + 42 | | """ + ) fun `times() works as expected`( - a: Number, - b: Number, - result: Number, + @ConvertWith(AmountConverter::class) a: Amount, + @ConvertWith(AmountConverter::class) b: Amount, + @ConvertWith(AmountConverter::class) result: Amount, ) { - val amountA = if (a is Amount) a else Amount.of(a.toDouble()) - val amountB = if (b is Amount) b else Amount.of(b.toDouble()) - val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) - assertEquals(amountResult, amountA.times(amountB)) + assertEquals(result, a.times(b)) } @ParameterizedTest - @MethodSource("generator for divideBy() works as expected") + @CsvSource( + delimiter = '|', textBlock = """ + 4.2 | 42 | 0.1 + 42 | 4.2 | 10 + 0 | 42 | 0 + 42 |0 | + | 42 | + 42 | | """ + ) fun `divideBy() works as expected`( - a: Number, - b: Number, - result: Number, + @ConvertWith(AmountConverter::class) a: Amount, + @ConvertWith(AmountConverter::class) b: Amount, + @ConvertWith(AmountConverter::class) result: Amount, ) { - val amountA = if (a is Amount) a else Amount.of(a.toDouble()) - val amountB = if (b is Amount) b else Amount.of(b.toDouble()) - val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) - assertEquals(amountResult, amountA.divideBy(amountB)) + assertEquals(result, a.divideBy(b)) } @ParameterizedTest - @MethodSource("generator for negate() works as expected") + @CsvSource( + delimiter = '|', textBlock = """ + 4.2 | -4.2 + -4.2 | 4.2 + 42 | -42 + 0 | 0 + -0 | 0 + | """ + ) fun `negate() works as expected`( - a: Number, - result: Number, + @ConvertWith(AmountConverter::class) a: Amount, + @ConvertWith(AmountConverter::class) result: Amount, ) { - val amountA = if (a is Amount) a else Amount.of(a.toDouble()) - val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) - assertEquals(amountResult, amountA.negate()) + assertEquals(result, a.negate()) } @ParameterizedTest - @MethodSource("generator for increasePercent() works as expected") + @CsvSource( + delimiter = '|', textBlock = """ + 4.2 | 10 | 4.62 + 0 | 42 | 0 + 42 | 0 | 42 + | 42 | + 42 | | 42 """ + ) fun `increasePercent() works as expected`( - a: Number, - b: Number, - result: Number, + @ConvertWith(AmountConverter::class) a: Amount, + @ConvertWith(AmountConverter::class) b: Amount, + @ConvertWith(AmountConverter::class) result: Amount, ) { - val amountA = if (a is Amount) a else Amount.of(a.toDouble()) - val amountB = if (b is Amount) b else Amount.of(b.toDouble()) - val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) - assertEquals(amountResult, amountA.increasePercent(amountB)) + + assertEquals(result, a.increasePercent(b)) } @ParameterizedTest - @MethodSource("generator for decreasePercent() works as expected") + @CsvSource( + delimiter = '|', textBlock = """ + 4.2 | 10 | 3.78 + 0 | 42 | 0 + 42 | 0 | 42 + | 42 | + 42 | | 42""" + ) fun `decreasePercent() works as expected`( - a: Number, - b: Number, - result: Number, + @ConvertWith(AmountConverter::class) a: Amount, + @ConvertWith(AmountConverter::class) b: Amount, + @ConvertWith(AmountConverter::class) result: Amount, ) { - val amountA = if (a is Amount) a else Amount.of(a.toDouble()) - val amountB = if (b is Amount) b else Amount.of(b.toDouble()) - val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) - assertEquals(amountResult, amountA.decreasePercent(amountB)) + + assertEquals(result, a.decreasePercent(b)) } @ParameterizedTest - @MethodSource("generator for percentageOf() works as expected") + @CsvSource( + delimiter = '|', textBlock = """ + 4.2 | 42 | 10 + 0 | 42 | 0 + 42 | 0 | + | 42 | + 42 | | """ + ) fun `percentageOf() works as expected`( - a: Number, - b: Number, - result: Number, + @ConvertWith(AmountConverter::class) a: Amount, + @ConvertWith(AmountConverter::class) b: Amount, + @ConvertWith(AmountConverter::class) result: Amount, ) { - val amountA = if (a is Amount) a else Amount.of(a.toDouble()) - val amountB = if (b is Amount) b else Amount.of(b.toDouble()) - val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) - assertEquals(amountResult, amountA.percentageOf(amountB)) + + assertEquals(result, a.percentageOf(b)) } @ParameterizedTest - @MethodSource("generator for percentageDifferenceOf() works as expected") + @CsvSource( + delimiter = '|', textBlock = """ + 4.62 | 4.2 | 10 + 0 | 42 | -100 + 42 | 0 | + | 42 | + 42 | | """ + ) fun `percentageDifferenceOf() works as expected`( - a: Number, - b: Number, - result: Number, + @ConvertWith(AmountConverter::class) a: Amount, + @ConvertWith(AmountConverter::class) b: Amount, + @ConvertWith(AmountConverter::class) result: Amount, ) { - val amountA = if (a is Amount) a else Amount.of(a.toDouble()) - val amountB = if (b is Amount) b else Amount.of(b.toDouble()) - val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) - assertEquals(amountResult, amountA.percentageDifferenceOf(amountB)) + + assertEquals(result, a.percentageDifferenceOf(b)) } @ParameterizedTest - @MethodSource("generator for toPercent() works as expected") + @CsvSource( + delimiter = '|', textBlock = """ + 0.42 | 42 + 1 | 100 + 2 | 200 + 0 | 0 + | """ + ) fun `toPercent() works as expected`( - a: Number, - result: Number, + @ConvertWith(AmountConverter::class) a: Amount, + @ConvertWith(AmountConverter::class) result: Amount, ) { - val amountA = if (a is Amount) a else Amount.of(a.toDouble()) - val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) - assertEquals(amountResult, amountA.toPercent()) + + assertEquals(result, a.toPercent()) } @ParameterizedTest - @MethodSource("generator for asDecimal() works as expected") + @CsvSource( + delimiter = '|', textBlock = """ + 42 | 0.42 + 100 | 1 + 200 | 2 + 0 | 0 + | """ + ) fun `asDecimal() works as expected`( - a: Number, - result: Number, + @ConvertWith(AmountConverter::class) a: Amount, + @ConvertWith(AmountConverter::class) result: Amount, ) { - val amountA = if (a is Amount) a else Amount.of(a.toDouble()) - val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) - assertEquals(amountResult, amountA.asDecimal()) + + assertEquals(result, a.asDecimal()) } @ParameterizedTest - @MethodSource("generator for remainder() works as expected") + @CsvSource( + delimiter = '|', textBlock = """ + 10 | 2 | 0 + 10 | 3 | 1 + 10 | 0 | + 0 | 10 | 0 + | 10 | + 10 | | """ + ) fun `remainder() works as expected`( - a: Number, - b: Number, - result: Number, + @ConvertWith(AmountConverter::class) a: Amount, + @ConvertWith(AmountConverter::class) b: Amount, + @ConvertWith(AmountConverter::class) result: Amount, ) { - val amountA = if (a is Amount) a else Amount.of(a.toDouble()) - val amountB = if (b is Amount) b else Amount.of(b.toDouble()) - val amountResult = if (result is Amount) result else Amount.of(result.toDouble()) - assertEquals(amountResult, amountA.remainder(amountB)) + + assertEquals(result, a.remainder(b)) } } From 0f78cf9dce34b98c6c97afc6b8fb9e395ba76e1e Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Wed, 6 Sep 2023 15:46:48 +0200 Subject: [PATCH 069/111] AmountConverter in extra file and fixed ugly formatting --- .../sirius/kernel/commons/AmountConverter.kt | 23 +++++++++++++++++++ .../sirius/kernel/commons/AmountTest.kt | 21 ++++------------- 2 files changed, 27 insertions(+), 17 deletions(-) create mode 100644 src/test/kotlin/sirius/kernel/commons/AmountConverter.kt diff --git a/src/test/kotlin/sirius/kernel/commons/AmountConverter.kt b/src/test/kotlin/sirius/kernel/commons/AmountConverter.kt new file mode 100644 index 00000000..9710f833 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/AmountConverter.kt @@ -0,0 +1,23 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons +import org.junit.jupiter.api.extension.ParameterContext +import org.junit.jupiter.params.converter.ArgumentConverter + +class AmountConverter : ArgumentConverter { + override fun convert(source: Any?, context: ParameterContext?): Any? { + if (source !is String) { + + return Amount.NOTHING + + } + return Amount.ofMachineString(source) + } + +} \ No newline at end of file diff --git a/src/test/kotlin/sirius/kernel/commons/AmountTest.kt b/src/test/kotlin/sirius/kernel/commons/AmountTest.kt index df6b181d..dd989933 100644 --- a/src/test/kotlin/sirius/kernel/commons/AmountTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/AmountTest.kt @@ -10,9 +10,7 @@ package sirius.kernel.commons import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith -import org.junit.jupiter.api.extension.ParameterContext import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.converter.ArgumentConverter import org.junit.jupiter.params.converter.ConvertWith import org.junit.jupiter.params.provider.CsvSource import sirius.kernel.SiriusExtension @@ -26,17 +24,6 @@ import kotlin.test.assertTrue * Tests the [Amount] class. */ -class AmountConverter : ArgumentConverter { - override fun convert(source: Any?, context: ParameterContext?): Any? { - if (source !is String) { - - return Amount.NOTHING - - } - return Amount.ofMachineString(source) - } - -} @ExtendWith(SiriusExtension::class) class AmountTest { @@ -178,10 +165,10 @@ class AmountTest { delimiter = '|', textBlock = """ 4.2 | 42 | 46.2 42 | 4.2 | 46.2 - 0 | 42 | 42 + 0 | 42 | 42 42 | 0 | 42 - | 42 | - 42 | | 42 """ + | 42 | + 42 | | 42 """ ) fun `add() works as expected`( @ConvertWith(AmountConverter::class) a: Amount, @@ -235,7 +222,7 @@ class AmountTest { 4.2 | 42 | 0.1 42 | 4.2 | 10 0 | 42 | 0 - 42 |0 | + 42 | 0 | | 42 | 42 | | """ ) From 5d3530f07f1919345950d210e8d45441d96936e3 Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Thu, 7 Sep 2023 08:22:52 +0200 Subject: [PATCH 070/111] removed Whitespaces --- src/test/kotlin/sirius/kernel/commons/AmountConverter.kt | 3 --- src/test/kotlin/sirius/kernel/commons/AmountTest.kt | 2 -- 2 files changed, 5 deletions(-) diff --git a/src/test/kotlin/sirius/kernel/commons/AmountConverter.kt b/src/test/kotlin/sirius/kernel/commons/AmountConverter.kt index 9710f833..b8477d54 100644 --- a/src/test/kotlin/sirius/kernel/commons/AmountConverter.kt +++ b/src/test/kotlin/sirius/kernel/commons/AmountConverter.kt @@ -13,11 +13,8 @@ import org.junit.jupiter.params.converter.ArgumentConverter class AmountConverter : ArgumentConverter { override fun convert(source: Any?, context: ParameterContext?): Any? { if (source !is String) { - return Amount.NOTHING - } return Amount.ofMachineString(source) } - } \ No newline at end of file diff --git a/src/test/kotlin/sirius/kernel/commons/AmountTest.kt b/src/test/kotlin/sirius/kernel/commons/AmountTest.kt index dd989933..9d9b9a0a 100644 --- a/src/test/kotlin/sirius/kernel/commons/AmountTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/AmountTest.kt @@ -19,12 +19,10 @@ import java.math.RoundingMode import kotlin.test.assertEquals import kotlin.test.assertTrue - /** * Tests the [Amount] class. */ - @ExtendWith(SiriusExtension::class) class AmountTest { From 7e0728f3593ff0d71e95113f8cab7881a02e27fc Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Thu, 7 Sep 2023 13:23:14 +0200 Subject: [PATCH 071/111] moved ManagedCacheSpec to kotlin file --- .../sirius/kernel/cache/ManagedCacheTest.kt | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/test/kotlin/sirius/kernel/cache/ManagedCacheTest.kt diff --git a/src/test/kotlin/sirius/kernel/cache/ManagedCacheTest.kt b/src/test/kotlin/sirius/kernel/cache/ManagedCacheTest.kt new file mode 100644 index 00000000..847224af --- /dev/null +++ b/src/test/kotlin/sirius/kernel/cache/ManagedCacheTest.kt @@ -0,0 +1,113 @@ +package sirius.kernel.cache + +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.cache + +import org.junit.jupiter.api.Tag +import sirius.kernel.commons.Strings +import sirius.kernel.commons.Tuple +import sirius.kernel.commons.Wait + +import java.util.function.BiFunction +import java.util.function.BiPredicate +import java.util.function.Function +import java.util.function.Predicate + +@Tag("nightly") +class ManagedCacheSpec extends BaseSpecification { + + def "test run eviction removes old entries"() { + given: + def cache = new ManagedCache("test-cache", null, null) + when: + cache.put("key1", "value1") + cache.put("key2", "value2") + Wait.millis(1001) + cache.put("key3", "value3") + cache.put("key4", "value4") + then: + cache.getSize() == 4 + cache.runEviction() + cache.getSize() == 2 + cache.get("key3") == "value3" + cache.get("key4") == "value4" + } + + def "optional value computer works"() { + given: + def valueComputer = { key -> + if (Strings.isEmpty(key)) { + return Optional.empty() + } + if (key.startsWith("empty")) { + return Optional.empty() + } else { + return Optional.of(key.toUpperCase()) + } + } + def cache = new ManagedCache("test-cache", OptionalValueComputer.of(valueComputer), null) + expect: + cache.get("") == null + cache.getOptional("key") == Optional.of("KEY") + cache.getOptional("empty_key") == Optional.empty() + } + + def "removeAll works as expected"() { + given: + ManagedCache> cache = new ManagedCache("test-cache", null, null) + cache.addRemover("FIRST", + { key, entry -> Strings.areEqual(key, entry.getValue().getFirst()) }) + cache.addRemover("SECOND", + { key, entry -> Strings.areEqual(key, entry.getValue().getSecond()) }) + + when: + cache.put("A", Tuple.create("0", "0")) + cache.put("B", Tuple.create("1", "2")) + cache.put("C", Tuple.create("2", "1")) + cache.put("D", Tuple.create("3", "3")) + and: "Remove all entries where the first is a '1' and then all where the second is a '1'" + cache.removeAll("FIRST", "1") + cache.removeAll("SECOND", "1") + then: "Ensure that the correct entries were removed and others remained in cache" + cache.get("A") != null + cache.get("B") == null + cache.get("C") == null + cache.get("D") != null + } + + def "remover builder works as expected"() { + given: + ManagedCache> cache = new ManagedCache("test-cache", null, null) + cache.addRemover("FILTER"). + filter({ selector, entry -> (entry.getKey() != selector) } as BiPredicate). + map({ entry -> entry.getValue() } as Function). + map({ tuple -> tuple.getFirst() } as Function). + map({ selector, value -> value + selector } as BiFunction). + removeIf({ x -> x.size() > 5 } as Predicate) + cache.addValueBasedRemover("REMOVE_ALWAYS"). + removeAlways({ selector, tuple -> tuple.getSecond() == selector } as BiPredicate). + removeIf({ false } as Predicate) + when: + cache.put("A", Tuple.create("gets ignored, ", "because the key is equal to the selector")) + cache.put("B", Tuple.create("gets ", "removed, because it's too long")) + cache.put("C", Tuple.create("does", " not get removed")) + cache.put("D", Tuple.create("B", "A")) + cache.put("E", Tuple.create("B", "C")) + and: + cache.removeAll("FILTER", "A") + cache.removeAll("REMOVE_ALWAYS", "C") + then: + cache.get("A") != null + cache.get("B") == null + cache.get("C") != null + cache.get("D") != null + cache.get("E") == null + } +} From 03df705654e6507f544c036de1120cb68033c028 Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Thu, 7 Sep 2023 13:34:54 +0200 Subject: [PATCH 072/111] Allows correctly fetching array node from constructed json tree Previously problems arose within the array helper methods when calling them on JSON nodes that contained pojo list nodes instead of jackson ArrayNodes. As we do construct JSON objects quite often we expanded the logic of these methods to also correctly convert such nodes to ArrayNodes so they are properly returned instead of an empty optional. Fixes: SIRI-841 --- src/main/java/sirius/kernel/commons/Json.java | 22 +++++---- .../kotlin/sirius/kernel/commons/JsonTest.kt | 46 +++++++++++++++++++ 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/main/java/sirius/kernel/commons/Json.java b/src/main/java/sirius/kernel/commons/Json.java index d8130859..79dd2b38 100644 --- a/src/main/java/sirius/kernel/commons/Json.java +++ b/src/main/java/sirius/kernel/commons/Json.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.POJONode; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import sirius.kernel.health.Exceptions; import sirius.kernel.health.Log; @@ -408,10 +409,7 @@ public static ArrayNode getArrayAtIndex(@Nonnull ArrayNode arrayNode, int index) @Nonnull public static Optional tryGetArrayAtIndex(@Nonnull ArrayNode arrayNode, int index) { JsonNode node = arrayNode.get(index); - if (node == null || !node.isArray()) { - return Optional.empty(); - } - return Optional.of((ArrayNode) node); + return tryNodeAsArray(node); } /** @@ -438,10 +436,7 @@ public static ArrayNode getArray(@Nonnull ObjectNode objectNode, String fieldNam @Nonnull public static Optional tryGetArray(@Nonnull ObjectNode objectNode, String fieldName) { JsonNode node = objectNode.get(fieldName); - if (node == null || !node.isArray()) { - return Optional.empty(); - } - return Optional.of((ArrayNode) node); + return tryNodeAsArray(node); } /** @@ -468,10 +463,17 @@ public static ArrayNode getArrayAt(@Nonnull JsonNode jsonNode, JsonPointer point @Nonnull public static Optional tryGetArrayAt(@Nonnull JsonNode jsonNode, JsonPointer pointer) { JsonNode node = jsonNode.at(pointer); - if (node == null || !node.isArray() || node.isMissingNode()) { + return tryNodeAsArray(node); + } + + private static Optional tryNodeAsArray(JsonNode node) { + if (node == null || node.isMissingNode()) { return Optional.empty(); } - return Optional.of((ArrayNode) node); + if (node instanceof POJONode pojoNode && pojoNode.getPojo() instanceof Collection collection) { + return Optional.of(MAPPER.valueToTree(collection)); + } + return node.isArray() ? Optional.of((ArrayNode) node) : Optional.empty(); } /** diff --git a/src/test/kotlin/sirius/kernel/commons/JsonTest.kt b/src/test/kotlin/sirius/kernel/commons/JsonTest.kt index 7cebacd3..dfd92eea 100644 --- a/src/test/kotlin/sirius/kernel/commons/JsonTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/JsonTest.kt @@ -215,6 +215,21 @@ class JsonTest { assertTrue(!missingArray.isPresent) } + @Test + fun `tryGetArrayAtIndex works with arrays as POJO Nodes`() { + val node = Json.createArray().addPOJO(listOf(1, 2, 3)).add(123) + + val presentArray: Optional = Json.tryGetArrayAtIndex(node, 0) + assertTrue(presentArray.isPresent) + assertEquals(3, presentArray.get().size()) + + val notAnArray: Optional = Json.tryGetArrayAtIndex(node, 1) + assertTrue(!notAnArray.isPresent) + + val missingArray: Optional = Json.tryGetArrayAtIndex(node, 2) + assertTrue(!missingArray.isPresent) + } + @Test fun `tryGetArray works as expected`() { val json = """{ "foo": [1, 2, 3], "bar": 123 }""" @@ -231,6 +246,21 @@ class JsonTest { assertTrue(!missingArray.isPresent) } + @Test + fun `tryGetArray works with arrays as POJO Nodes`() { + val node = Json.createObject().putPOJO("foo", listOf(1, 2, 3)).put("bar", 123) + + val presentArray: Optional = Json.tryGetArray(node, "foo") + assertTrue(presentArray.isPresent) + assertEquals(3, presentArray.get().size()) + + val notAnArray: Optional = Json.tryGetArray(node, "bar") + assertTrue(!notAnArray.isPresent) + + val missingArray: Optional = Json.tryGetArray(node, "baz") + assertTrue(!missingArray.isPresent) + } + @Test fun `tryGetArrayAt works as expected`() { val json = """{ "foo": [1, 2, 3], "bar": 123 }""" @@ -247,6 +277,22 @@ class JsonTest { assertTrue(!missingArray.isPresent) } + @Test + fun `tryGetArrayAt works with arrays as POJO Nodes`() { + val node = Json.createObject() + .set("nested", Json.createObject().putPOJO("foo", listOf(1, 2, 3)).put("bar", 123)) + + val presentArray: Optional = Json.tryGetArrayAt(node, Json.createPointer("nested/foo")) + assertTrue(presentArray.isPresent) + assertEquals(3, presentArray.get().size()) + + val notAnArray: Optional = Json.tryGetArrayAt(node, Json.createPointer("nested/bar")) + assertTrue(!notAnArray.isPresent) + + val missingArray: Optional = Json.tryGetArrayAt(node, Json.createPointer("nested/baz")) + assertTrue(!missingArray.isPresent) + } + @Test fun `tryGetAtIndex works as expected`() { val json = """[1, null]""" From a4d80cce84a54858191fdd1a7481e5c506c2ba6e Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Fri, 8 Sep 2023 11:15:33 +0200 Subject: [PATCH 073/111] adapted Kotlin Syntax --- .../sirius/kernel/cache/ManagedCacheTest.kt | 160 ++++++++++-------- 1 file changed, 94 insertions(+), 66 deletions(-) diff --git a/src/test/kotlin/sirius/kernel/cache/ManagedCacheTest.kt b/src/test/kotlin/sirius/kernel/cache/ManagedCacheTest.kt index 847224af..e8da8c2c 100644 --- a/src/test/kotlin/sirius/kernel/cache/ManagedCacheTest.kt +++ b/src/test/kotlin/sirius/kernel/cache/ManagedCacheTest.kt @@ -1,5 +1,3 @@ -package sirius.kernel.cache - /* * Made with all the love in the world * by scireum in Remshalden, Germany @@ -10,104 +8,134 @@ package sirius.kernel.cache package sirius.kernel.cache -import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import sirius.kernel.SiriusExtension import sirius.kernel.commons.Strings import sirius.kernel.commons.Tuple import sirius.kernel.commons.Wait +import java.util.* +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals -import java.util.function.BiFunction -import java.util.function.BiPredicate -import java.util.function.Function -import java.util.function.Predicate - -@Tag("nightly") -class ManagedCacheSpec extends BaseSpecification { +/** + * Tests the [AdvancedDateParser] class. + */ +@ExtendWith(SiriusExtension::class) +class ManagedCacheTest { - def "test run eviction removes old entries"() { - given: - def cache = new ManagedCache("test-cache", null, null) - when: + @Test + fun `test run eviction removes old entries`() { + val cache: ManagedCache = ManagedCache("test-cache", null, null) cache.put("key1", "value1") cache.put("key2", "value2") Wait.millis(1001) cache.put("key3", "value3") cache.put("key4", "value4") - then: - cache.getSize() == 4 + assertEquals(4, cache.getSize()) cache.runEviction() - cache.getSize() == 2 - cache.get("key3") == "value3" - cache.get("key4") == "value4" + assertEquals(2, cache.getSize()) + assertEquals("value3", cache.get("key3")) + assertEquals("value4", cache.get("key4")) } - def "optional value computer works"() { - given: - def valueComputer = { key -> - if (Strings.isEmpty(key)) { - return Optional.empty() - } - if (key.startsWith("empty")) { - return Optional.empty() + @Test + fun `optional value computer works`() { + val valueComputer = { key: String -> + if (key.isEmpty() || key.startsWith("empty")) { + Optional.empty() } else { - return Optional.of(key.toUpperCase()) + Optional.of(key.toUpperCase()) } } - def cache = new ManagedCache("test-cache", OptionalValueComputer.of(valueComputer), null) - expect: - cache.get("") == null - cache.getOptional("key") == Optional.of("KEY") - cache.getOptional("empty_key") == Optional.empty() + val cache = ManagedCache("test-cache", OptionalValueComputer.of(valueComputer), null) + assertEquals(null, cache.get("")) + assertEquals(Optional.of("KEY"), cache.getOptional("key")) + assertEquals(Optional.empty(), cache.getOptional("empty_key")) + } - def "removeAll works as expected"() { - given: - ManagedCache> cache = new ManagedCache("test-cache", null, null) + @Test + fun `removeAll works as expected`() { + val cache: ManagedCache> = ManagedCache("test-cache", null, null) cache.addRemover("FIRST", - { key, entry -> Strings.areEqual(key, entry.getValue().getFirst()) }) + { key, entry -> + Strings.areEqual(key, entry.getValue()?.getFirst()) + }) cache.addRemover("SECOND", - { key, entry -> Strings.areEqual(key, entry.getValue().getSecond()) }) - - when: + { key, entry -> + Strings.areEqual(key, entry.getValue()?.getSecond()) + }) cache.put("A", Tuple.create("0", "0")) cache.put("B", Tuple.create("1", "2")) cache.put("C", Tuple.create("2", "1")) cache.put("D", Tuple.create("3", "3")) - and: "Remove all entries where the first is a '1' and then all where the second is a '1'" + `Remove all entries where the first is a '1' and then all where the second is a '1'`(cache) + `Ensure that the correct entries were removed and others remained in cache`(cache) + } + + @DisplayName("Remove all entries where the first is a '1' and then all where the second is a '1'") + private fun `Remove all entries where the first is a '1' and then all where the second is a '1'`(cache: ManagedCache>) { cache.removeAll("FIRST", "1") cache.removeAll("SECOND", "1") - then: "Ensure that the correct entries were removed and others remained in cache" - cache.get("A") != null - cache.get("B") == null - cache.get("C") == null - cache.get("D") != null } - def "remover builder works as expected"() { - given: - ManagedCache> cache = new ManagedCache("test-cache", null, null) - cache.addRemover("FILTER"). - filter({ selector, entry -> (entry.getKey() != selector) } as BiPredicate). - map({ entry -> entry.getValue() } as Function). - map({ tuple -> tuple.getFirst() } as Function). - map({ selector, value -> value + selector } as BiFunction). - removeIf({ x -> x.size() > 5 } as Predicate) - cache.addValueBasedRemover("REMOVE_ALWAYS"). - removeAlways({ selector, tuple -> tuple.getSecond() == selector } as BiPredicate). - removeIf({ false } as Predicate) - when: + @DisplayName("Ensure that the correct entries were removed and others remained in cache") + private fun `Ensure that the correct entries were removed and others remained in cache`(cache: ManagedCache>) { + assertNotEquals(null, cache.get("A")) + assertEquals(null, cache.get("B")) + assertEquals(null, cache.get("C")) + assertNotEquals(null, cache.get("D")) + } + + @Test + fun `remover builder works as expected`() { + val cache: ManagedCache> = ManagedCache("test-cache", null, null) cache.put("A", Tuple.create("gets ignored, ", "because the key is equal to the selector")) cache.put("B", Tuple.create("gets ", "removed, because it's too long")) cache.put("C", Tuple.create("does", " not get removed")) cache.put("D", Tuple.create("B", "A")) cache.put("E", Tuple.create("B", "C")) - and: + + // defines a remover, that allows to define a key value, which should not be removed + cache.addRemover("FILTER") + .filter { selector: String, entry: CacheEntry> -> + (entry.getKey() != selector) + } + // get tuple consisting of two strings + .map { entry: CacheEntry?> -> + entry.getValue() + } + .map { tuple: Tuple? -> + // get first value of tuple (which is a string) + tuple?.first + }.map( + // add the selector (key of the cache entry) and the first value of the string + // for the cache value B this would be: "B" + "gets " = "Bgets " + { selector, value -> + value + selector + } + ).removeIf( + // if the resulting string, f.E. "Bgets " is larger than 5, remove entry + { x -> + x.length > 5 + } + ) + + cache.addValueBasedRemover("REMOVE_ALWAYS").removeAlways({ selector, tuple -> + tuple.getSecond() == selector + }).removeIf({ tuple -> + false + }) + cache.removeAll("FILTER", "A") cache.removeAll("REMOVE_ALWAYS", "C") - then: - cache.get("A") != null - cache.get("B") == null - cache.get("C") != null - cache.get("D") != null - cache.get("E") == null + + assertNotEquals(null, cache.get("A")) + assertEquals(null, cache.get("B")) + assertNotEquals(null, cache.get("C")) + assertNotEquals(null, cache.get("D")) + assertEquals(null, cache.get("E")) } } From 1881460becd9a4829eea09bd46b257ffc9452590 Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Fri, 8 Sep 2023 11:15:52 +0200 Subject: [PATCH 074/111] deleted ManagedCacheSpec.groovy --- .../kernel/cache/ManagedCacheSpec.groovy | 112 ------------------ 1 file changed, 112 deletions(-) delete mode 100644 src/test/java/sirius/kernel/cache/ManagedCacheSpec.groovy diff --git a/src/test/java/sirius/kernel/cache/ManagedCacheSpec.groovy b/src/test/java/sirius/kernel/cache/ManagedCacheSpec.groovy deleted file mode 100644 index ba9873bf..00000000 --- a/src/test/java/sirius/kernel/cache/ManagedCacheSpec.groovy +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.cache - -import org.junit.jupiter.api.Tag -import sirius.kernel.BaseSpecification -import sirius.kernel.commons.Strings -import sirius.kernel.commons.Tuple -import sirius.kernel.commons.Wait - -import java.util.function.BiFunction -import java.util.function.BiPredicate -import java.util.function.Function -import java.util.function.Predicate - -@Tag("nightly") -class ManagedCacheSpec extends BaseSpecification { - - def "test run eviction removes old entries"() { - given: - def cache = new ManagedCache("test-cache", null, null) - when: - cache.put("key1", "value1") - cache.put("key2", "value2") - Wait.millis(1001) - cache.put("key3", "value3") - cache.put("key4", "value4") - then: - cache.getSize() == 4 - cache.runEviction() - cache.getSize() == 2 - cache.get("key3") == "value3" - cache.get("key4") == "value4" - } - - def "optional value computer works"() { - given: - def valueComputer = { key -> - if (Strings.isEmpty(key)) { - return Optional.empty() - } - if (key.startsWith("empty")) { - return Optional.empty() - } else { - return Optional.of(key.toUpperCase()) - } - } - def cache = new ManagedCache("test-cache", OptionalValueComputer.of(valueComputer), null) - expect: - cache.get("") == null - cache.getOptional("key") == Optional.of("KEY") - cache.getOptional("empty_key") == Optional.empty() - } - - def "removeAll works as expected"() { - given: - ManagedCache> cache = new ManagedCache("test-cache", null, null) - cache.addRemover("FIRST", - { key, entry -> Strings.areEqual(key, entry.getValue().getFirst()) }) - cache.addRemover("SECOND", - { key, entry -> Strings.areEqual(key, entry.getValue().getSecond()) }) - - when: - cache.put("A", Tuple.create("0", "0")) - cache.put("B", Tuple.create("1", "2")) - cache.put("C", Tuple.create("2", "1")) - cache.put("D", Tuple.create("3", "3")) - and: "Remove all entries where the first is a '1' and then all where the second is a '1'" - cache.removeAll("FIRST", "1") - cache.removeAll("SECOND", "1") - then: "Ensure that the correct entries were removed and others remained in cache" - cache.get("A") != null - cache.get("B") == null - cache.get("C") == null - cache.get("D") != null - } - - def "remover builder works as expected"() { - given: - ManagedCache> cache = new ManagedCache("test-cache", null, null) - cache.addRemover("FILTER"). - filter({ selector, entry -> (entry.getKey() != selector) } as BiPredicate). - map({ entry -> entry.getValue() } as Function). - map({ tuple -> tuple.getFirst() } as Function). - map({ selector, value -> value + selector } as BiFunction). - removeIf({ x -> x.size() > 5 } as Predicate) - cache.addValueBasedRemover("REMOVE_ALWAYS"). - removeAlways({ selector, tuple -> tuple.getSecond() == selector } as BiPredicate). - removeIf({ false } as Predicate) - when: - cache.put("A", Tuple.create("gets ignored, ", "because the key is equal to the selector")) - cache.put("B", Tuple.create("gets ", "removed, because it's too long")) - cache.put("C", Tuple.create("does", " not get removed")) - cache.put("D", Tuple.create("B", "A")) - cache.put("E", Tuple.create("B", "C")) - and: - cache.removeAll("FILTER", "A") - cache.removeAll("REMOVE_ALWAYS", "C") - then: - cache.get("A") != null - cache.get("B") == null - cache.get("C") != null - cache.get("D") != null - cache.get("E") == null - } -} From dceecd7e8154596cae68a6e8395a19a80f70833f Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Fri, 8 Sep 2023 13:53:48 +0200 Subject: [PATCH 075/111] split last complex test into two and simplifyed by using ManagedCache --- .../sirius/kernel/cache/ManagedCacheTest.kt | 77 +++++++++++-------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/src/test/kotlin/sirius/kernel/cache/ManagedCacheTest.kt b/src/test/kotlin/sirius/kernel/cache/ManagedCacheTest.kt index e8da8c2c..056b617d 100644 --- a/src/test/kotlin/sirius/kernel/cache/ManagedCacheTest.kt +++ b/src/test/kotlin/sirius/kernel/cache/ManagedCacheTest.kt @@ -8,9 +8,9 @@ package sirius.kernel.cache -import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith +import sirius.kernel.NightlyTest import sirius.kernel.SiriusExtension import sirius.kernel.commons.Strings import sirius.kernel.commons.Tuple @@ -22,7 +22,9 @@ import kotlin.test.assertNotEquals /** * Tests the [AdvancedDateParser] class. */ + @ExtendWith(SiriusExtension::class) +@NightlyTest class ManagedCacheTest { @Test @@ -71,18 +73,12 @@ class ManagedCacheTest { cache.put("B", Tuple.create("1", "2")) cache.put("C", Tuple.create("2", "1")) cache.put("D", Tuple.create("3", "3")) - `Remove all entries where the first is a '1' and then all where the second is a '1'`(cache) - `Ensure that the correct entries were removed and others remained in cache`(cache) - } - @DisplayName("Remove all entries where the first is a '1' and then all where the second is a '1'") - private fun `Remove all entries where the first is a '1' and then all where the second is a '1'`(cache: ManagedCache>) { + //Remove all entries where the first is a '1' and then all where the second is a '1' cache.removeAll("FIRST", "1") cache.removeAll("SECOND", "1") - } - @DisplayName("Ensure that the correct entries were removed and others remained in cache") - private fun `Ensure that the correct entries were removed and others remained in cache`(cache: ManagedCache>) { + //Ensure that the correct entries were removed and others remained in cache assertNotEquals(null, cache.get("A")) assertEquals(null, cache.get("B")) assertEquals(null, cache.get("C")) @@ -91,51 +87,66 @@ class ManagedCacheTest { @Test fun `remover builder works as expected`() { - val cache: ManagedCache> = ManagedCache("test-cache", null, null) - cache.put("A", Tuple.create("gets ignored, ", "because the key is equal to the selector")) - cache.put("B", Tuple.create("gets ", "removed, because it's too long")) - cache.put("C", Tuple.create("does", " not get removed")) - cache.put("D", Tuple.create("B", "A")) - cache.put("E", Tuple.create("B", "C")) + val cache: ManagedCache = ManagedCache("test-cache", null, null) + cache.put("A", "1") + cache.put("B", "12") + cache.put("C", "123") + cache.put("D", "1234") + cache.put("E", "12345") // defines a remover, that allows to define a key value, which should not be removed cache.addRemover("FILTER") - .filter { selector: String, entry: CacheEntry> -> + .filter { selector, entry -> (entry.getKey() != selector) } - // get tuple consisting of two strings - .map { entry: CacheEntry?> -> + // get value of managed cache + .map { entry -> entry.getValue() } - .map { tuple: Tuple? -> - // get first value of tuple (which is a string) - tuple?.first - }.map( - // add the selector (key of the cache entry) and the first value of the string - // for the cache value B this would be: "B" + "gets " = "Bgets " + .map( + // add key + value of the cache Entry, for example: "E" + 12345 { selector, value -> value + selector } ).removeIf( - // if the resulting string, f.E. "Bgets " is larger than 5, remove entry + // if the resulting string, f.E. "E12345" is larger than 5, remove entry { x -> x.length > 5 } ) - cache.addValueBasedRemover("REMOVE_ALWAYS").removeAlways({ selector, tuple -> - tuple.getSecond() == selector - }).removeIf({ tuple -> - false - }) - cache.removeAll("FILTER", "A") - cache.removeAll("REMOVE_ALWAYS", "C") assertNotEquals(null, cache.get("A")) - assertEquals(null, cache.get("B")) + assertNotEquals(null, cache.get("B")) assertNotEquals(null, cache.get("C")) assertNotEquals(null, cache.get("D")) assertEquals(null, cache.get("E")) } + + @Test + fun `valueBasedRemover works as expected`() { + val cache: ManagedCache> = ManagedCache("test-cache", null, null) + cache.put("Key1", Tuple.create("1, ", "A")) + cache.put("Key2", Tuple.create("2 ", "B")) + cache.put("Key3", Tuple.create("3", "C")) + cache.put("Key4", Tuple.create("4", "D")) + cache.put("Key5", Tuple.create("5", "E")) + + cache.addValueBasedRemover("REMOVE_ALWAYS") + .removeAlways({ selector, tuple -> + tuple.getSecond() == selector + }).removeIf({ tuple -> + false + }) + + cache.removeAll("REMOVE_ALWAYS", "C") + cache.removeAll("REMOVE_ALWAYS", "B") + + assertNotEquals(null, cache.get("Key1")) + assertEquals(null, cache.get("Key2")) + assertEquals(null, cache.get("Key3")) + assertNotEquals(null, cache.get("Key4")) + assertNotEquals(null, cache.get("Key5")) + } } From 0c2610af1ae9b322f84adeff7caf3898878354ea Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Fri, 8 Sep 2023 14:13:22 +0200 Subject: [PATCH 076/111] minor formatting --- .../sirius/kernel/cache/ManagedCacheTest.kt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/test/kotlin/sirius/kernel/cache/ManagedCacheTest.kt b/src/test/kotlin/sirius/kernel/cache/ManagedCacheTest.kt index 056b617d..d9a408a7 100644 --- a/src/test/kotlin/sirius/kernel/cache/ManagedCacheTest.kt +++ b/src/test/kotlin/sirius/kernel/cache/ManagedCacheTest.kt @@ -96,13 +96,17 @@ class ManagedCacheTest { // defines a remover, that allows to define a key value, which should not be removed cache.addRemover("FILTER") - .filter { selector, entry -> - (entry.getKey() != selector) - } - // get value of managed cache - .map { entry -> - entry.getValue() - } + .filter( + { selector, entry -> + entry.getKey() != selector + } + ) + .map( + // get value of managed cache + { entry -> + entry.getValue() + } + ) .map( // add key + value of the cache Entry, for example: "E" + 12345 { selector, value -> From 22697675e700acf85bf250f2f3be40f3adc17497 Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 11 Sep 2023 08:20:03 +0200 Subject: [PATCH 077/111] moved content of PullBasedSpliteratorTest to kotlin --- .../commons/PullBasedSpliteratorTest.kt | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/test/kotlin/sirius/kernel/commons/PullBasedSpliteratorTest.kt diff --git a/src/test/kotlin/sirius/kernel/commons/PullBasedSpliteratorTest.kt b/src/test/kotlin/sirius/kernel/commons/PullBasedSpliteratorTest.kt new file mode 100644 index 00000000..78eb35ad --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/PullBasedSpliteratorTest.kt @@ -0,0 +1,58 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons; + +import org.junit.jupiter.api.Test; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class PullBasedSpliteratorTest { + + private static class TestSpliterator extends PullBasedSpliterator { + + private int index; + + @Nullable + @Override + protected Iterator pullNextBlock() { + if (index < 10) { + List result = new ArrayList<>(); + for (int i = index; i < index + 5; i++) { + result.add(i); + } + index += 5; + + return result.iterator(); + } + + return Collections.emptyIterator(); + } + + @Override + public int characteristics() { + return 0; + } + } + + @Test + void streamWorksProperly() { + assertEquals(10, StreamSupport.stream(new TestSpliterator(), false).count()); + assertEquals(StreamSupport.stream(new TestSpliterator(), false).collect(Collectors.toList()), + Stream.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9).collect(Collectors.toList())); + } +} From 274fb85b3a30b2bbd1c74ae6539a71f288109796 Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 11 Sep 2023 08:21:45 +0200 Subject: [PATCH 078/111] Converted code to kotlin --- .../commons/PullBasedSpliteratorTest.kt | 69 ++++++++----------- 1 file changed, 28 insertions(+), 41 deletions(-) diff --git a/src/test/kotlin/sirius/kernel/commons/PullBasedSpliteratorTest.kt b/src/test/kotlin/sirius/kernel/commons/PullBasedSpliteratorTest.kt index 78eb35ad..c383f229 100644 --- a/src/test/kotlin/sirius/kernel/commons/PullBasedSpliteratorTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/PullBasedSpliteratorTest.kt @@ -5,54 +5,41 @@ * Copyright by scireum GmbH * http://www.scireum.de - info@scireum.de */ - -package sirius.kernel.commons; - -import org.junit.jupiter.api.Test; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class PullBasedSpliteratorTest { - - private static class TestSpliterator extends PullBasedSpliterator { - - private int index; - - @Nullable - @Override - protected Iterator pullNextBlock() { +package sirius.kernel.commons + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.util.* +import java.util.stream.Collectors +import java.util.stream.Stream +import java.util.stream.StreamSupport + +internal class PullBasedSpliteratorTest { + private class TestSpliterator : PullBasedSpliterator() { + private var index = 0 + override fun pullNextBlock(): Iterator? { if (index < 10) { - List result = new ArrayList<>(); - for (int i = index; i < index + 5; i++) { - result.add(i); + val result: MutableList = ArrayList() + for (i in index until index + 5) { + result.add(i) } - index += 5; - - return result.iterator(); + index += 5 + return result.iterator() } - - return Collections.emptyIterator(); + return Collections.emptyIterator() } - @Override - public int characteristics() { - return 0; + override fun characteristics(): Int { + return 0 } } @Test - void streamWorksProperly() { - assertEquals(10, StreamSupport.stream(new TestSpliterator(), false).count()); - assertEquals(StreamSupport.stream(new TestSpliterator(), false).collect(Collectors.toList()), - Stream.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9).collect(Collectors.toList())); + fun streamWorksProperly() { + Assertions.assertEquals(10, StreamSupport.stream(TestSpliterator(), false).count()) + Assertions.assertEquals( + StreamSupport.stream(TestSpliterator(), false).collect(Collectors.toList()), + Stream.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9).collect(Collectors.toList()) + ) } -} +} \ No newline at end of file From 843823067a96036df398fd1c03b4db092585c118 Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 11 Sep 2023 08:22:02 +0200 Subject: [PATCH 079/111] Deleted PullBasedSpliteratorTest.java --- .../commons/PullBasedSpliteratorTest.java | 58 ------------------- 1 file changed, 58 deletions(-) delete mode 100644 src/test/java/sirius/kernel/commons/PullBasedSpliteratorTest.java diff --git a/src/test/java/sirius/kernel/commons/PullBasedSpliteratorTest.java b/src/test/java/sirius/kernel/commons/PullBasedSpliteratorTest.java deleted file mode 100644 index d0dd6bf1..00000000 --- a/src/test/java/sirius/kernel/commons/PullBasedSpliteratorTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.commons; - -import org.junit.jupiter.api.Test; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class PullBasedSpliteratorTest { - - private static class TestSpliterator extends PullBasedSpliterator { - - private int index; - - @Nullable - @Override - protected Iterator pullNextBlock() { - if (index < 10) { - List result = new ArrayList<>(); - for (int i = index; i < index + 5; i++) { - result.add(i); - } - index += 5; - - return result.iterator(); - } - - return Collections.emptyIterator(); - } - - @Override - public int characteristics() { - return 0; - } - } - - @Test - void streamWorksProperly() { - assertEquals(10, StreamSupport.stream(new TestSpliterator(), false).count()); - assertEquals(StreamSupport.stream(new TestSpliterator(), false).collect(Collectors.toList()), - Stream.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9).collect(Collectors.toList())); - } -} From e609efcf8c8d2250ab4add5ed2f48d5f2f45fb1c Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 11 Sep 2023 08:23:36 +0200 Subject: [PATCH 080/111] Some formatting --- .../kotlin/sirius/kernel/commons/PullBasedSpliteratorTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/sirius/kernel/commons/PullBasedSpliteratorTest.kt b/src/test/kotlin/sirius/kernel/commons/PullBasedSpliteratorTest.kt index c383f229..b2ec666c 100644 --- a/src/test/kotlin/sirius/kernel/commons/PullBasedSpliteratorTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/PullBasedSpliteratorTest.kt @@ -42,4 +42,4 @@ internal class PullBasedSpliteratorTest { Stream.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9).collect(Collectors.toList()) ) } -} \ No newline at end of file +} From a51fa584e092a0e27c9d9b61d5f797b11aa71eff Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 11 Sep 2023 11:05:15 +0200 Subject: [PATCH 081/111] moved BackgroundLoopSpec to kotlin file --- .../sirius/kernel/async/BackgroundLoopTest.kt | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/test/kotlin/sirius/kernel/async/BackgroundLoopTest.kt diff --git a/src/test/kotlin/sirius/kernel/async/BackgroundLoopTest.kt b/src/test/kotlin/sirius/kernel/async/BackgroundLoopTest.kt new file mode 100644 index 00000000..d9274fb0 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/async/BackgroundLoopTest.kt @@ -0,0 +1,41 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.async + +import org.junit.jupiter.api.Tag +import sirius.kernel.BaseSpecification +import sirius.kernel.commons.Wait + +@Tag("nightly") +class BackgroundLoopSpec extends BaseSpecification { + + def "BackgroundLoop limits to max frequency"() { + given: + int currentCounter = FastTestLoop.counter + when: + Wait.seconds(10) + int delta = FastTestLoop.counter - currentCounter + then: "the background loop executed enough calls (should be 10 but we're a bit tolerant here)" + delta >= 8 + and: "the background loop was limited not to execute too often (should be 10 but we're a bit tolerant here)" + delta <= 12 + } + + def "BackgroundLoop executes as fast as possible if loop is slow"() { + given: + int currentCounter = SlowTestLoop.counter + when: + Wait.seconds(10) + int delta = SlowTestLoop.counter - currentCounter + then: "the background loop executed enough calls (should be 5 but we're a bit tolerant here)" + delta >= 3 + and: "the background loop was limited not to execute too often (should be 5 but we're a bit tolerant here)" + delta <= 6 + } +} From fb2cee4708364a5478f4d3015f67a92644d24dfe Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 11 Sep 2023 11:32:55 +0200 Subject: [PATCH 082/111] converted Syntax to kotlin --- .../sirius/kernel/async/BackgroundLoopTest.kt | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/test/kotlin/sirius/kernel/async/BackgroundLoopTest.kt b/src/test/kotlin/sirius/kernel/async/BackgroundLoopTest.kt index d9274fb0..63242f8a 100644 --- a/src/test/kotlin/sirius/kernel/async/BackgroundLoopTest.kt +++ b/src/test/kotlin/sirius/kernel/async/BackgroundLoopTest.kt @@ -9,33 +9,34 @@ package sirius.kernel.async import org.junit.jupiter.api.Tag -import sirius.kernel.BaseSpecification +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import sirius.kernel.SiriusExtension import sirius.kernel.commons.Wait +import kotlin.test.assertTrue @Tag("nightly") -class BackgroundLoopSpec extends BaseSpecification { - - def "BackgroundLoop limits to max frequency"() { - given: - int currentCounter = FastTestLoop.counter - when: - Wait.seconds(10) - int delta = FastTestLoop.counter - currentCounter - then: "the background loop executed enough calls (should be 10 but we're a bit tolerant here)" - delta >= 8 - and: "the background loop was limited not to execute too often (should be 10 but we're a bit tolerant here)" - delta <= 12 +@ExtendWith(SiriusExtension::class) +class BackgroundLoopSpec { + @Test + fun `BackgroundLoop limits to max frequency`() { + val currentCounter = FastTestLoop.counter.toInt() + Wait.seconds(10.0) + val delta = FastTestLoop.counter.toInt() - currentCounter + //the background loop executed enough calls (should be 10 but we're a bit tolerant here) + assertTrue { delta >= 8 } + //the background loop was limited not to execute too often (should be 10 but we're a bit tolerant here) + assertTrue { delta <= 12 } } - def "BackgroundLoop executes as fast as possible if loop is slow"() { - given: - int currentCounter = SlowTestLoop.counter - when: - Wait.seconds(10) - int delta = SlowTestLoop.counter - currentCounter - then: "the background loop executed enough calls (should be 5 but we're a bit tolerant here)" - delta >= 3 - and: "the background loop was limited not to execute too often (should be 5 but we're a bit tolerant here)" - delta <= 6 + @Test + fun `BackgroundLoop executes as fast as possible if loop is slow`() { + val currentCounter = SlowTestLoop.counter.toInt() + Wait.seconds(10.0) + val delta = SlowTestLoop.counter.toInt() - currentCounter + // the background loop executed enough calls (should be 10 but we're a bit tolerant here) + assertTrue { delta >= 3 } + // the background loop was limited not to execute too often (should be 10 but we're a bit tolerant here) + assertTrue { delta <= 6 } } } From 16ac3471296dce89da8a120d330ce2cbb879c862 Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 11 Sep 2023 11:33:09 +0200 Subject: [PATCH 083/111] deleted BackgroundLoopSpec.groovy --- .../kernel/async/BackgroundLoopSpec.groovy | 41 ------------------- 1 file changed, 41 deletions(-) delete mode 100644 src/test/java/sirius/kernel/async/BackgroundLoopSpec.groovy diff --git a/src/test/java/sirius/kernel/async/BackgroundLoopSpec.groovy b/src/test/java/sirius/kernel/async/BackgroundLoopSpec.groovy deleted file mode 100644 index 82d92413..00000000 --- a/src/test/java/sirius/kernel/async/BackgroundLoopSpec.groovy +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Made with all the love in the world - * by scireum in Remshalden, Germany - * - * Copyright by scireum GmbH - * http://www.scireum.de - info@scireum.de - */ - -package sirius.kernel.async - -import org.junit.jupiter.api.Tag -import sirius.kernel.BaseSpecification -import sirius.kernel.commons.Wait - -@Tag("nightly") -class BackgroundLoopSpec extends BaseSpecification { - - def "BackgroundLoop limits to max frequency"() { - given: - int currentCounter = FastTestLoop.counter - when: - Wait.seconds(10) - int delta = FastTestLoop.counter - currentCounter - then: "the background loop executed enough calls (should be 10 but we're a bit tolerant here)" - delta >= 8 - and: "the background loop was limited not to execute too often (should be 10 but we're a bit tolerant here)" - delta <= 12 - } - - def "BackgroundLoop executes as fast as possible if loop is slow"() { - given: - int currentCounter = SlowTestLoop.counter - when: - Wait.seconds(10) - int delta = SlowTestLoop.counter - currentCounter - then: "the background loop executed enough calls (should be 5 but we're a bit tolerant here)" - delta >= 3 - and: "the background loop was limited not to execute too often (should be 5 but we're a bit tolerant here)" - delta <= 6 - } -} From 6d4eaf910f3793747eea5f257553a3b108f94cec Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 11 Sep 2023 11:35:47 +0200 Subject: [PATCH 084/111] corrected comments --- src/test/kotlin/sirius/kernel/async/BackgroundLoopTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/sirius/kernel/async/BackgroundLoopTest.kt b/src/test/kotlin/sirius/kernel/async/BackgroundLoopTest.kt index 63242f8a..c25861b4 100644 --- a/src/test/kotlin/sirius/kernel/async/BackgroundLoopTest.kt +++ b/src/test/kotlin/sirius/kernel/async/BackgroundLoopTest.kt @@ -34,9 +34,9 @@ class BackgroundLoopSpec { val currentCounter = SlowTestLoop.counter.toInt() Wait.seconds(10.0) val delta = SlowTestLoop.counter.toInt() - currentCounter - // the background loop executed enough calls (should be 10 but we're a bit tolerant here) + //the background loop executed enough calls (should be 5 but we're a bit tolerant here) assertTrue { delta >= 3 } - // the background loop was limited not to execute too often (should be 10 but we're a bit tolerant here) + //the background loop was limited not to execute too often (should be 5 but we're a bit tolerant here) assertTrue { delta <= 6 } } } From 1643c8e3f114efc4f8dafdfa9ab7e241d4857caf Mon Sep 17 00:00:00 2001 From: MOOOOOSER Date: Mon, 11 Sep 2023 12:16:14 +0200 Subject: [PATCH 085/111] added correct decorator for nightly test --- src/test/kotlin/sirius/kernel/async/BackgroundLoopTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/sirius/kernel/async/BackgroundLoopTest.kt b/src/test/kotlin/sirius/kernel/async/BackgroundLoopTest.kt index c25861b4..6fd75259 100644 --- a/src/test/kotlin/sirius/kernel/async/BackgroundLoopTest.kt +++ b/src/test/kotlin/sirius/kernel/async/BackgroundLoopTest.kt @@ -8,14 +8,14 @@ package sirius.kernel.async -import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith +import sirius.kernel.NightlyTest import sirius.kernel.SiriusExtension import sirius.kernel.commons.Wait import kotlin.test.assertTrue -@Tag("nightly") +@NightlyTest @ExtendWith(SiriusExtension::class) class BackgroundLoopSpec { @Test From c91a6ff401412bd24b49728c3754a85f98bdeb2b Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 12 Sep 2023 16:36:38 +0200 Subject: [PATCH 086/111] =?UTF-8?q?Fixes=20typos=20=F0=9F=A7=91=E2=80=8D?= =?UTF-8?q?=F0=9F=8F=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sirius/kernel/health/console/TimerCommand.java | 2 +- src/main/java/sirius/kernel/timer/Timers.java | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/sirius/kernel/health/console/TimerCommand.java b/src/main/java/sirius/kernel/health/console/TimerCommand.java index 10dd4bff..8e5f9426 100644 --- a/src/main/java/sirius/kernel/health/console/TimerCommand.java +++ b/src/main/java/sirius/kernel/health/console/TimerCommand.java @@ -22,7 +22,7 @@ /** * Console command which reports the last execution of the timer tasks. *

- * It also permits to call an timer out of schedule + * It also permits to call a timer out of schedule. */ @Register public class TimerCommand implements Command { diff --git a/src/main/java/sirius/kernel/timer/Timers.java b/src/main/java/sirius/kernel/timer/Timers.java index 9a6cb6c3..92f6f510 100644 --- a/src/main/java/sirius/kernel/timer/Timers.java +++ b/src/main/java/sirius/kernel/timer/Timers.java @@ -157,7 +157,7 @@ private static class WatchedResource { } /** - * Returns the timestamp of the last execution of the 10 second timer. + * Returns the timestamp of the last execution of the 10-second timer. * * @return a textual representation of the last execution of the ten seconds timer. Returns "-" if the timer didn't * run yet. @@ -170,9 +170,9 @@ public String getLastTenSecondsExecution() { } /** - * Returns the timestamp of the last execution of the one minute timer. + * Returns the timestamp of the last execution of the one-minute timer. * - * @return a textual representation of the last execution of the one minute timer. Returns "-" if the timer didn't + * @return a textual representation of the last execution of the one-minute timer. Returns "-" if the timer didn't * run yet. */ public String getLastOneMinuteExecution() { @@ -196,9 +196,9 @@ public String getLastTenMinutesExecution() { } /** - * Returns the timestamp of the last execution of the one hour timer. + * Returns the timestamp of the last execution of the one-hour timer. * - * @return a textual representation of the last execution of the one hour timer. Returns "-" if the timer didn't + * @return a textual representation of the last execution of the one-hour timer. Returns "-" if the timer didn't * run yet. */ public String getLastHourExecution() { From 045ab982075444951e017a3a90d411e47231b7de Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 12 Sep 2023 16:41:49 +0200 Subject: [PATCH 087/111] =?UTF-8?q?Improves=20variable=20names=20=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ts` → `timers` `params` → `parameters` --- .../kernel/health/console/TimerCommand.java | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/main/java/sirius/kernel/health/console/TimerCommand.java b/src/main/java/sirius/kernel/health/console/TimerCommand.java index 8e5f9426..e815ed61 100644 --- a/src/main/java/sirius/kernel/health/console/TimerCommand.java +++ b/src/main/java/sirius/kernel/health/console/TimerCommand.java @@ -34,52 +34,53 @@ public class TimerCommand implements Command { private static final String USAGE = "Usage: timer all|oneMinute|tenMinutes|oneHour|everyDay "; @Part - private Timers ts; + private Timers timers; @Override - public void execute(Output output, String... params) throws Exception { - if (params.length == 0) { + public void execute(Output output, String... parameters) throws Exception { + if (parameters.length == 0) { output.line(USAGE); - } else if (!ACCEPTED_PARAMS.contains(params[0])) { - output.apply("'%s' is not an accepted parameter!", params[0]); + } else if (!ACCEPTED_PARAMS.contains(parameters[0])) { + output.apply("'%s' is not an accepted parameter!", parameters[0]); output.line(USAGE); } else { - if ("all".equalsIgnoreCase(params[0]) || "oneMinute".equalsIgnoreCase(params[0])) { + if ("all".equalsIgnoreCase(parameters[0]) || "oneMinute".equalsIgnoreCase(parameters[0])) { output.line("Executing one minute timers..."); - ts.runOneMinuteTimers(); + timers.runOneMinuteTimers(); } - if ("all".equalsIgnoreCase(params[0]) || "tenMinutes".equalsIgnoreCase(params[0])) { + if ("all".equalsIgnoreCase(parameters[0]) || "tenMinutes".equalsIgnoreCase(parameters[0])) { output.line("Executing ten minute timers..."); - ts.runTenMinuteTimers(); + timers.runTenMinuteTimers(); } - if ("all".equalsIgnoreCase(params[0]) || "oneHour".equalsIgnoreCase(params[0])) { + if ("all".equalsIgnoreCase(parameters[0]) || "oneHour".equalsIgnoreCase(parameters[0])) { output.line("Executing one hour timers..."); - ts.runOneHourTimers(); + timers.runOneHourTimers(); } - if ("everyDay".equalsIgnoreCase(params[0])) { - int currentHour = Values.of(params).at(1).asInt(25); + if ("everyDay".equalsIgnoreCase(parameters[0])) { + int currentHour = Values.of(parameters).at(1).asInt(25); output.line("Executing daily timers for hour: " + currentHour); - ts.runEveryDayTimers(currentHour); + timers.runEveryDayTimers(currentHour); } } output.blankLine(); output.line("System Timers - Last Execution"); output.separator(); - output.apply(LINE_FORMAT, "One-Minute", ts.getLastOneMinuteExecution()); - output.apply(LINE_FORMAT, "Ten-Minutes", ts.getLastTenMinutesExecution()); - output.apply(LINE_FORMAT, "One-Hour", ts.getLastHourExecution()); + output.apply(LINE_FORMAT, "One-Minute", timers.getLastOneMinuteExecution()); + output.apply(LINE_FORMAT, "Ten-Minutes", timers.getLastTenMinutesExecution()); + output.apply(LINE_FORMAT, "One-Hour", timers.getLastHourExecution()); output.separator(); output.blankLine(); output.line("Daily Tasks"); output.separator(); - ts.getDailyTasks() - .stream() - .map(task -> Tuple.create(Sirius.getSettings().getInt(Timers.TIMER_DAILY_PREFIX + task.getConfigKeyName()), - task.getConfigKeyName())) - .sorted(Comparator.comparingInt(Tuple::getFirst)) - .forEach(hourAndTask -> output.apply("%2sh: %s", hourAndTask.getFirst(), hourAndTask.getSecond())); + timers.getDailyTasks() + .stream() + .map(task -> Tuple.create(Sirius.getSettings() + .getInt(Timers.TIMER_DAILY_PREFIX + task.getConfigKeyName()), + task.getConfigKeyName())) + .sorted(Comparator.comparingInt(Tuple::getFirst)) + .forEach(hourAndTask -> output.apply("%2sh: %s", hourAndTask.getFirst(), hourAndTask.getSecond())); output.separator(); } From a0065e78f5b4cf1d716dafe489b1c106824f5cc1 Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 12 Sep 2023 16:52:27 +0200 Subject: [PATCH 088/111] =?UTF-8?q?Improves=20variable=20names=20=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `res` → `resource` --- src/main/java/sirius/kernel/timer/Timers.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/sirius/kernel/timer/Timers.java b/src/main/java/sirius/kernel/timer/Timers.java index 92f6f510..7e4ed3cc 100644 --- a/src/main/java/sirius/kernel/timer/Timers.java +++ b/src/main/java/sirius/kernel/timer/Timers.java @@ -232,16 +232,16 @@ public void run() { private void watchLoadedResources() { Thread.currentThread().setName("Resource-Watch"); - for (WatchedResource res : loadedFiles) { - long lastModified = res.file.lastModified(); - if (lastModified > res.lastModified) { - res.lastModified = res.file.lastModified(); - LOG.INFO("Reloading: %s", res.file.toString()); + for (WatchedResource resource : loadedFiles) { + long lastModified = resource.file.lastModified(); + if (lastModified > resource.lastModified) { + resource.lastModified = resource.file.lastModified(); + LOG.INFO("Reloading: %s", resource.file.toString()); try { - res.callback.run(); + resource.callback.run(); } catch (Exception exception) { Exceptions.handle() - .withSystemErrorMessage("Error reloading %s: %s (%s)", res.file.toString()) + .withSystemErrorMessage("Error reloading %s: %s (%s)", resource.file.toString()) .error(exception) .handle(); } @@ -295,12 +295,12 @@ public void stopped() { @Explain("Resources are only collected once at startup, so there is no performance hotspot") public void addWatchedResource(@Nonnull URL url, @Nonnull Runnable callback) { try { - WatchedResource res = new WatchedResource(); + WatchedResource resource = new WatchedResource(); File file = new File(url.toURI()); - res.file = file; - res.callback = callback; - res.lastModified = file.lastModified(); - loadedFiles.add(res); + resource.file = file; + resource.callback = callback; + resource.lastModified = file.lastModified(); + loadedFiles.add(resource); } catch (IllegalArgumentException | URISyntaxException exception) { Exceptions.ignore(exception); Exceptions.handle() From d86ab41ec8ce171c5b1e439aecf22dd1a08b264c Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 12 Sep 2023 16:57:54 +0200 Subject: [PATCH 089/111] =?UTF-8?q?Provides=20constructor=20for=20`Watched?= =?UTF-8?q?Resource`=20=F0=9F=9A=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The constructor now encapsulates the creation of the internal state – as it should. --- src/main/java/sirius/kernel/timer/Timers.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/sirius/kernel/timer/Timers.java b/src/main/java/sirius/kernel/timer/Timers.java index 7e4ed3cc..89f1b7d4 100644 --- a/src/main/java/sirius/kernel/timer/Timers.java +++ b/src/main/java/sirius/kernel/timer/Timers.java @@ -151,9 +151,15 @@ public void run() { * Used to monitor a resource for changes */ private static class WatchedResource { - private File file; + private final File file; private long lastModified; - private Runnable callback; + private final Runnable callback; + + private WatchedResource(File file, Runnable callback) { + this.file = file; + this.lastModified = file.lastModified(); + this.callback = callback; + } } /** @@ -295,12 +301,7 @@ public void stopped() { @Explain("Resources are only collected once at startup, so there is no performance hotspot") public void addWatchedResource(@Nonnull URL url, @Nonnull Runnable callback) { try { - WatchedResource resource = new WatchedResource(); - File file = new File(url.toURI()); - resource.file = file; - resource.callback = callback; - resource.lastModified = file.lastModified(); - loadedFiles.add(resource); + loadedFiles.add(new WatchedResource(new File(url.toURI()), callback)); } catch (IllegalArgumentException | URISyntaxException exception) { Exceptions.ignore(exception); Exceptions.handle() From 129a71c10ca775d09cc3cc15ca1b379eb346e5f8 Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 12 Sep 2023 17:00:21 +0200 Subject: [PATCH 090/111] =?UTF-8?q?Replaces=20loop=20with=20sexy=20`forEac?= =?UTF-8?q?h()`=20call=20=F0=9F=92=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/sirius/kernel/timer/Timers.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/sirius/kernel/timer/Timers.java b/src/main/java/sirius/kernel/timer/Timers.java index 89f1b7d4..cba1ed98 100644 --- a/src/main/java/sirius/kernel/timer/Timers.java +++ b/src/main/java/sirius/kernel/timer/Timers.java @@ -238,7 +238,7 @@ public void run() { private void watchLoadedResources() { Thread.currentThread().setName("Resource-Watch"); - for (WatchedResource resource : loadedFiles) { + loadedFiles.forEach(resource -> { long lastModified = resource.file.lastModified(); if (lastModified > resource.lastModified) { resource.lastModified = resource.file.lastModified(); @@ -252,7 +252,7 @@ private void watchLoadedResources() { .handle(); } } - } + }); } private void startTimer() { From a6e435acfb2ab09302a2d006e596289b69920f04 Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 12 Sep 2023 17:04:03 +0200 Subject: [PATCH 091/111] =?UTF-8?q?Moves=20private=20methods=20to=20the=20?= =?UTF-8?q?end=20of=20the=20file=20=F0=9F=91=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/sirius/kernel/timer/Timers.java | 172 +++++++++--------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/src/main/java/sirius/kernel/timer/Timers.java b/src/main/java/sirius/kernel/timer/Timers.java index cba1ed98..89f7cfc9 100644 --- a/src/main/java/sirius/kernel/timer/Timers.java +++ b/src/main/java/sirius/kernel/timer/Timers.java @@ -224,54 +224,6 @@ public void started() { } } - private void startResourceWatcher() { - if (reloadTimer == null) { - reloadTimer = new Timer(true); - reloadTimer.schedule(new TimerTask() { - @Override - public void run() { - watchLoadedResources(); - } - }, RELOAD_INTERVAL, RELOAD_INTERVAL); - } - } - - private void watchLoadedResources() { - Thread.currentThread().setName("Resource-Watch"); - loadedFiles.forEach(resource -> { - long lastModified = resource.file.lastModified(); - if (lastModified > resource.lastModified) { - resource.lastModified = resource.file.lastModified(); - LOG.INFO("Reloading: %s", resource.file.toString()); - try { - resource.callback.run(); - } catch (Exception exception) { - Exceptions.handle() - .withSystemErrorMessage("Error reloading %s: %s (%s)", resource.file.toString()) - .error(exception) - .handle(); - } - } - }); - } - - private void startTimer() { - try { - timerLock.lock(); - try { - if (timer != null) { - timer.cancel(); - } - timer = new Timer(true); - timer.schedule(new InnerTimerTask(), TEN_SECONDS_IN_MILLIS, TEN_SECONDS_IN_MILLIS); - } finally { - timerLock.unlock(); - } - } catch (Exception t) { - Exceptions.handle(LOG, t); - } - } - @Override public void stopped() { try { @@ -331,29 +283,6 @@ public void runOneMinuteTimers() { lastOneMinuteExecution = timeProvider.currentTimeMillis(); } - private void executeTask(final TimedTask task) { - tasks.executor(TIMER) - .dropOnOverload(() -> Exceptions.handle() - .to(LOG) - .withSystemErrorMessage( - "Dropping timer task '%s' (%s) due to system overload!", - task, - task.getClass()) - .handle()) - .start(() -> { - try { - Watch w = Watch.start(); - task.runTimer(); - if (w.elapsed(TimeUnit.SECONDS, false) > 1) { - LOG.WARN("TimedTask '%s' (%s) took over a second to complete! " - + "Consider executing the work in a separate executor!", task, task.getClass()); - } - } catch (Exception t) { - Exceptions.handle(LOG, t); - } - }); - } - /** * Executes all ten minutes timers (implementing EveryTenMinutes) now (out of schedule). */ @@ -395,6 +324,92 @@ public Collection getDailyTasks() { return Collections.unmodifiableCollection(everyDay.getParts()); } + /** + * Determines the execution hour (0..23) in which the given task is to be executed. + * + * @param task the task to check + * @return the execution hour wrapped as optional or an empty optional if the config is missing + */ + public Optional getExecutionHour(EveryDay task) { + String configPath = TIMER_DAILY_PREFIX + task.getConfigKeyName(); + if (!Sirius.getSettings().getConfig().hasPath(configPath)) { + return Optional.empty(); + } + + return Optional.of(Sirius.getSettings().getInt(configPath)); + } + + private void startResourceWatcher() { + if (reloadTimer == null) { + reloadTimer = new Timer(true); + reloadTimer.schedule(new TimerTask() { + @Override + public void run() { + watchLoadedResources(); + } + }, RELOAD_INTERVAL, RELOAD_INTERVAL); + } + } + + private void watchLoadedResources() { + Thread.currentThread().setName("Resource-Watch"); + loadedFiles.forEach(resource -> { + long lastModified = resource.file.lastModified(); + if (lastModified > resource.lastModified) { + resource.lastModified = resource.file.lastModified(); + LOG.INFO("Reloading: %s", resource.file.toString()); + try { + resource.callback.run(); + } catch (Exception exception) { + Exceptions.handle() + .withSystemErrorMessage("Error reloading %s: %s (%s)", resource.file.toString()) + .error(exception) + .handle(); + } + } + }); + } + + private void startTimer() { + try { + timerLock.lock(); + try { + if (timer != null) { + timer.cancel(); + } + timer = new Timer(true); + timer.schedule(new InnerTimerTask(), TEN_SECONDS_IN_MILLIS, TEN_SECONDS_IN_MILLIS); + } finally { + timerLock.unlock(); + } + } catch (Exception t) { + Exceptions.handle(LOG, t); + } + } + + private void executeTask(final TimedTask task) { + tasks.executor(TIMER) + .dropOnOverload(() -> Exceptions.handle() + .to(LOG) + .withSystemErrorMessage( + "Dropping timer task '%s' (%s) due to system overload!", + task, + task.getClass()) + .handle()) + .start(() -> { + try { + Watch w = Watch.start(); + task.runTimer(); + if (w.elapsed(TimeUnit.SECONDS, false) > 1) { + LOG.WARN("TimedTask '%s' (%s) took over a second to complete! " + + "Consider executing the work in a separate executor!", task, task.getClass()); + } + } catch (Exception t) { + Exceptions.handle(LOG, t); + } + }); + } + private void runDailyTimer(int currentHour, EveryDay task) { Optional executionHour = getExecutionHour(task); if (executionHour.isEmpty()) { @@ -414,19 +429,4 @@ private void runDailyTimer(int currentHour, EveryDay task) { executeTask(task); } - - /** - * Determines the execution hour (0..23) in which the given task is to be executed. - * - * @param task the task to check - * @return the execution hour wrapped as optional or an empty optional if the config is missing - */ - public Optional getExecutionHour(EveryDay task) { - String configPath = TIMER_DAILY_PREFIX + task.getConfigKeyName(); - if (!Sirius.getSettings().getConfig().hasPath(configPath)) { - return Optional.empty(); - } - - return Optional.of(Sirius.getSettings().getInt(configPath)); - } } From be73ebf0eda4d78bcb1445b5709abbc982152ada Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 12 Sep 2023 17:15:05 +0200 Subject: [PATCH 092/111] =?UTF-8?q?Adds=20option=20to=20force=20execution?= =?UTF-8?q?=20of=20daily=20tasks=20=F0=9F=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This will be required for an extended `timer` command. --- src/main/java/sirius/kernel/timer/Timers.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/java/sirius/kernel/timer/Timers.java b/src/main/java/sirius/kernel/timer/Timers.java index 89f7cfc9..68118296 100644 --- a/src/main/java/sirius/kernel/timer/Timers.java +++ b/src/main/java/sirius/kernel/timer/Timers.java @@ -310,8 +310,20 @@ public void runOneHourTimers() { * out-of-schedule eecution, this can be set to any value. */ public void runEveryDayTimers(int currentHour) { + runEveryDayTimers(currentHour, false); + } + + /** + * Executes all daily timers (implementing EveryDay) if applicable, or if outOfASchedule is true. + * + * @param currentHour determines the current hour. Most probably this will be wall-clock time. However, for + * out-of-schedule eecution, this can be set to any value. + * @param forced if true, the task will be executed even if it is not scheduled according to + * {@link Orchestration#shouldRunDailyTask(String)} + */ + public void runEveryDayTimers(int currentHour, boolean forced) { for (final EveryDay task : getDailyTasks()) { - runDailyTimer(currentHour, task); + runDailyTimer(currentHour, task, forced); } } @@ -410,7 +422,7 @@ private void executeTask(final TimedTask task) { }); } - private void runDailyTimer(int currentHour, EveryDay task) { + private void runDailyTimer(int currentHour, EveryDay task, boolean forced) { Optional executionHour = getExecutionHour(task); if (executionHour.isEmpty()) { LOG.WARN("Skipping daily timer %s as config key '%s' is missing!", @@ -423,7 +435,7 @@ private void runDailyTimer(int currentHour, EveryDay task) { return; } - if (orchestration != null && !orchestration.shouldRunDailyTask(task.getConfigKeyName())) { + if (!forced && orchestration != null && !orchestration.shouldRunDailyTask(task.getConfigKeyName())) { return; } From 5c50cc0af3d8ff7cecdc942a1304eedebbe8be22 Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 12 Sep 2023 17:36:17 +0200 Subject: [PATCH 093/111] =?UTF-8?q?Refactors=20processing=20to=20use=20a?= =?UTF-8?q?=20list=20=F0=9F=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This will enable optional parameters later. --- .../kernel/health/console/TimerCommand.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/main/java/sirius/kernel/health/console/TimerCommand.java b/src/main/java/sirius/kernel/health/console/TimerCommand.java index e815ed61..ca98b9df 100644 --- a/src/main/java/sirius/kernel/health/console/TimerCommand.java +++ b/src/main/java/sirius/kernel/health/console/TimerCommand.java @@ -16,7 +16,9 @@ import sirius.kernel.timer.Timers; import javax.annotation.Nonnull; +import java.util.ArrayList; import java.util.Comparator; +import java.util.List; import java.util.Set; /** @@ -38,26 +40,29 @@ public class TimerCommand implements Command { @Override public void execute(Output output, String... parameters) throws Exception { - if (parameters.length == 0) { + List parameterList = new ArrayList<>(List.of(parameters)); + String scope = parameterList.isEmpty() ? "" : parameterList.get(0); + + if (parameterList.isEmpty()) { output.line(USAGE); - } else if (!ACCEPTED_PARAMS.contains(parameters[0])) { - output.apply("'%s' is not an accepted parameter!", parameters[0]); + } else if (!ACCEPTED_PARAMS.contains(scope)) { + output.apply("'%s' is not an accepted parameter!", scope); output.line(USAGE); } else { - if ("all".equalsIgnoreCase(parameters[0]) || "oneMinute".equalsIgnoreCase(parameters[0])) { + if ("all".equalsIgnoreCase(scope) || "oneMinute".equalsIgnoreCase(scope)) { output.line("Executing one minute timers..."); timers.runOneMinuteTimers(); } - if ("all".equalsIgnoreCase(parameters[0]) || "tenMinutes".equalsIgnoreCase(parameters[0])) { + if ("all".equalsIgnoreCase(scope) || "tenMinutes".equalsIgnoreCase(scope)) { output.line("Executing ten minute timers..."); timers.runTenMinuteTimers(); } - if ("all".equalsIgnoreCase(parameters[0]) || "oneHour".equalsIgnoreCase(parameters[0])) { + if ("all".equalsIgnoreCase(scope) || "oneHour".equalsIgnoreCase(scope)) { output.line("Executing one hour timers..."); timers.runOneHourTimers(); } - if ("everyDay".equalsIgnoreCase(parameters[0])) { - int currentHour = Values.of(parameters).at(1).asInt(25); + if ("everyDay".equalsIgnoreCase(scope)) { + int currentHour = Values.of(parameterList).at(1).asInt(25); output.line("Executing daily timers for hour: " + currentHour); timers.runEveryDayTimers(currentHour); } From 706318ef1147c30a01093f2ecb19268bf0318768 Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 12 Sep 2023 17:43:06 +0200 Subject: [PATCH 094/111] =?UTF-8?q?Makes=20scope=20check=20case-insensitiv?= =?UTF-8?q?e=20=F0=9F=A4=B7=E2=80=8D=E2=99=82=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Further down the line, in the `else` branch, the matching ignores the case anyways. We are thus currently too restrictive in this first test. --- .../sirius/kernel/health/console/TimerCommand.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/sirius/kernel/health/console/TimerCommand.java b/src/main/java/sirius/kernel/health/console/TimerCommand.java index ca98b9df..87726d60 100644 --- a/src/main/java/sirius/kernel/health/console/TimerCommand.java +++ b/src/main/java/sirius/kernel/health/console/TimerCommand.java @@ -20,6 +20,8 @@ import java.util.Comparator; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Console command which reports the last execution of the timer tasks. @@ -31,7 +33,11 @@ public class TimerCommand implements Command { private static final String LINE_FORMAT = "%20s %-30s"; - private static final Set ACCEPTED_PARAMS = Set.of("all", "oneMinute", "tenMinutes", "oneHour", "everyDay"); + // matching is intentionally case-insensitive, and we compare lower-case values + private static final Set ACCEPTED_PARAMS = + Stream.of("all", "oneMinute", "tenMinutes", "oneHour", "everyDay") + .map(String::toLowerCase) + .collect(Collectors.toSet()); private static final String USAGE = "Usage: timer all|oneMinute|tenMinutes|oneHour|everyDay "; @@ -45,7 +51,7 @@ public void execute(Output output, String... parameters) throws Exception { if (parameterList.isEmpty()) { output.line(USAGE); - } else if (!ACCEPTED_PARAMS.contains(scope)) { + } else if (!ACCEPTED_PARAMS.contains(scope.toLowerCase())) { output.apply("'%s' is not an accepted parameter!", scope); output.line(USAGE); } else { From 6bdfe4e45d0e80e1be715bb08d5f04830cb87572 Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 12 Sep 2023 17:44:56 +0200 Subject: [PATCH 095/111] =?UTF-8?q?Evaluates=20and=20forwards=20the=20`for?= =?UTF-8?q?ce`=20flag=20=E2=9C=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kernel/health/console/TimerCommand.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/java/sirius/kernel/health/console/TimerCommand.java b/src/main/java/sirius/kernel/health/console/TimerCommand.java index 87726d60..36531449 100644 --- a/src/main/java/sirius/kernel/health/console/TimerCommand.java +++ b/src/main/java/sirius/kernel/health/console/TimerCommand.java @@ -39,7 +39,10 @@ public class TimerCommand implements Command { .map(String::toLowerCase) .collect(Collectors.toSet()); - private static final String USAGE = "Usage: timer all|oneMinute|tenMinutes|oneHour|everyDay "; + private static final String FORCE_PARAM_LONG = "--force"; + private static final String FORCE_PARAM_SHORT = "-f"; + + private static final String USAGE = "Usage: timer [-f] all|oneMinute|tenMinutes|oneHour|everyDay "; @Part private Timers timers; @@ -48,6 +51,7 @@ public class TimerCommand implements Command { public void execute(Output output, String... parameters) throws Exception { List parameterList = new ArrayList<>(List.of(parameters)); String scope = parameterList.isEmpty() ? "" : parameterList.get(0); + boolean forced = extractForceParameter(parameterList); if (parameterList.isEmpty()) { output.line(USAGE); @@ -70,7 +74,7 @@ public void execute(Output output, String... parameters) throws Exception { if ("everyDay".equalsIgnoreCase(scope)) { int currentHour = Values.of(parameterList).at(1).asInt(25); output.line("Executing daily timers for hour: " + currentHour); - timers.runEveryDayTimers(currentHour); + timers.runEveryDayTimers(currentHour, forced); } } @@ -106,4 +110,18 @@ public String getName() { public String getDescription() { return "Reports the last timer runs and executes them out of schedule."; } + + private boolean extractForceParameter(List parameterList) { + if (parameterList.contains(FORCE_PARAM_LONG)) { + parameterList.remove(FORCE_PARAM_LONG); + return true; + } + + if (parameterList.contains(FORCE_PARAM_SHORT)) { + parameterList.remove(FORCE_PARAM_SHORT); + return true; + } + + return false; + } } From 197289a86341c4ee53431ce2496ccd014df5647a Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 12 Sep 2023 17:55:07 +0200 Subject: [PATCH 096/111] =?UTF-8?q?Fixes=20order=20of=20statements=20?= =?UTF-8?q?=F0=9F=91=A8=E2=80=8D=F0=9F=9A=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We first need to remove the `force` flag before storing the `scope`. --- src/main/java/sirius/kernel/health/console/TimerCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/sirius/kernel/health/console/TimerCommand.java b/src/main/java/sirius/kernel/health/console/TimerCommand.java index 36531449..62fbe257 100644 --- a/src/main/java/sirius/kernel/health/console/TimerCommand.java +++ b/src/main/java/sirius/kernel/health/console/TimerCommand.java @@ -50,8 +50,8 @@ public class TimerCommand implements Command { @Override public void execute(Output output, String... parameters) throws Exception { List parameterList = new ArrayList<>(List.of(parameters)); - String scope = parameterList.isEmpty() ? "" : parameterList.get(0); boolean forced = extractForceParameter(parameterList); + String scope = parameterList.isEmpty() ? "" : parameterList.get(0); if (parameterList.isEmpty()) { output.line(USAGE); From ec1034d56b30b9c32d5e10eb82a6a5bf88d25271 Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 12 Sep 2023 17:56:10 +0200 Subject: [PATCH 097/111] =?UTF-8?q?Indicates=20to=20the=20user=20if=20the?= =?UTF-8?q?=20timer=20is=20executed=20forcibly=20=F0=9F=9A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/sirius/kernel/health/console/TimerCommand.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/sirius/kernel/health/console/TimerCommand.java b/src/main/java/sirius/kernel/health/console/TimerCommand.java index 62fbe257..314df044 100644 --- a/src/main/java/sirius/kernel/health/console/TimerCommand.java +++ b/src/main/java/sirius/kernel/health/console/TimerCommand.java @@ -73,7 +73,8 @@ public void execute(Output output, String... parameters) throws Exception { } if ("everyDay".equalsIgnoreCase(scope)) { int currentHour = Values.of(parameterList).at(1).asInt(25); - output.line("Executing daily timers for hour: " + currentHour); + String forcedMessage = forced ? " (forced)" : ""; + output.line("Executing daily timers for hour: " + currentHour + forcedMessage); timers.runEveryDayTimers(currentHour, forced); } } From 324ab033c8e0e629a617a56f7725203dd1026767 Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 12 Sep 2023 18:01:49 +0200 Subject: [PATCH 098/111] =?UTF-8?q?Renames=20parameter=20=F0=9F=93=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sirius/kernel/health/console/TimerCommand.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/sirius/kernel/health/console/TimerCommand.java b/src/main/java/sirius/kernel/health/console/TimerCommand.java index 314df044..31619ae9 100644 --- a/src/main/java/sirius/kernel/health/console/TimerCommand.java +++ b/src/main/java/sirius/kernel/health/console/TimerCommand.java @@ -112,14 +112,14 @@ public String getDescription() { return "Reports the last timer runs and executes them out of schedule."; } - private boolean extractForceParameter(List parameterList) { - if (parameterList.contains(FORCE_PARAM_LONG)) { - parameterList.remove(FORCE_PARAM_LONG); + private boolean extractForceParameter(List parameters) { + if (parameters.contains(FORCE_PARAM_LONG)) { + parameters.remove(FORCE_PARAM_LONG); return true; } - if (parameterList.contains(FORCE_PARAM_SHORT)) { - parameterList.remove(FORCE_PARAM_SHORT); + if (parameters.contains(FORCE_PARAM_SHORT)) { + parameters.remove(FORCE_PARAM_SHORT); return true; } From cb21403dc1e57611b672fef8ec8f77bd3e5f989e Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 12 Sep 2023 18:03:09 +0200 Subject: [PATCH 099/111] =?UTF-8?q?Refactors=20scope=20extraction=20into?= =?UTF-8?q?=20helper=20=F0=9F=93=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sirius/kernel/health/console/TimerCommand.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/sirius/kernel/health/console/TimerCommand.java b/src/main/java/sirius/kernel/health/console/TimerCommand.java index 31619ae9..ca3cbec8 100644 --- a/src/main/java/sirius/kernel/health/console/TimerCommand.java +++ b/src/main/java/sirius/kernel/health/console/TimerCommand.java @@ -51,7 +51,7 @@ public class TimerCommand implements Command { public void execute(Output output, String... parameters) throws Exception { List parameterList = new ArrayList<>(List.of(parameters)); boolean forced = extractForceParameter(parameterList); - String scope = parameterList.isEmpty() ? "" : parameterList.get(0); + String scope = extractScope(parameterList); if (parameterList.isEmpty()) { output.line(USAGE); @@ -72,7 +72,7 @@ public void execute(Output output, String... parameters) throws Exception { timers.runOneHourTimers(); } if ("everyDay".equalsIgnoreCase(scope)) { - int currentHour = Values.of(parameterList).at(1).asInt(25); + int currentHour = Values.of(parameterList).at(0).asInt(25); String forcedMessage = forced ? " (forced)" : ""; output.line("Executing daily timers for hour: " + currentHour + forcedMessage); timers.runEveryDayTimers(currentHour, forced); @@ -112,6 +112,16 @@ public String getDescription() { return "Reports the last timer runs and executes them out of schedule."; } + private String extractScope(List parameters) { + if (parameters.isEmpty()) { + return ""; + } + + String scope = parameters.get(0); + parameters.remove(0); + return scope; + } + private boolean extractForceParameter(List parameters) { if (parameters.contains(FORCE_PARAM_LONG)) { parameters.remove(FORCE_PARAM_LONG); From 6cacbee2fc1dc17a5daff71d7383efee339b0f51 Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 12 Sep 2023 18:04:25 +0200 Subject: [PATCH 100/111] =?UTF-8?q?Refactors=20actual=20command=20executio?= =?UTF-8?q?n=20into=20helper=20=F0=9F=93=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kernel/health/console/TimerCommand.java | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/main/java/sirius/kernel/health/console/TimerCommand.java b/src/main/java/sirius/kernel/health/console/TimerCommand.java index ca3cbec8..d9591708 100644 --- a/src/main/java/sirius/kernel/health/console/TimerCommand.java +++ b/src/main/java/sirius/kernel/health/console/TimerCommand.java @@ -59,24 +59,7 @@ public void execute(Output output, String... parameters) throws Exception { output.apply("'%s' is not an accepted parameter!", scope); output.line(USAGE); } else { - if ("all".equalsIgnoreCase(scope) || "oneMinute".equalsIgnoreCase(scope)) { - output.line("Executing one minute timers..."); - timers.runOneMinuteTimers(); - } - if ("all".equalsIgnoreCase(scope) || "tenMinutes".equalsIgnoreCase(scope)) { - output.line("Executing ten minute timers..."); - timers.runTenMinuteTimers(); - } - if ("all".equalsIgnoreCase(scope) || "oneHour".equalsIgnoreCase(scope)) { - output.line("Executing one hour timers..."); - timers.runOneHourTimers(); - } - if ("everyDay".equalsIgnoreCase(scope)) { - int currentHour = Values.of(parameterList).at(0).asInt(25); - String forcedMessage = forced ? " (forced)" : ""; - output.line("Executing daily timers for hour: " + currentHour + forcedMessage); - timers.runEveryDayTimers(currentHour, forced); - } + executeCommand(scope, parameterList, forced, output); } output.blankLine(); @@ -135,4 +118,25 @@ private boolean extractForceParameter(List parameters) { return false; } + + private void executeCommand(String scope, List parameters, boolean forced, Output output) { + if ("all".equalsIgnoreCase(scope) || "oneMinute".equalsIgnoreCase(scope)) { + output.line("Executing one minute timers..."); + timers.runOneMinuteTimers(); + } + if ("all".equalsIgnoreCase(scope) || "tenMinutes".equalsIgnoreCase(scope)) { + output.line("Executing ten minute timers..."); + timers.runTenMinuteTimers(); + } + if ("all".equalsIgnoreCase(scope) || "oneHour".equalsIgnoreCase(scope)) { + output.line("Executing one hour timers..."); + timers.runOneHourTimers(); + } + if ("everyDay".equalsIgnoreCase(scope)) { + int currentHour = Values.of(parameters).at(0).asInt(25); + String forcedMessage = forced ? " (forced)" : ""; + output.line("Executing daily timers for hour: " + currentHour + forcedMessage); + timers.runEveryDayTimers(currentHour, forced); + } + } } From 97913343af4b2db3fc54f5df75980038f284f258 Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Tue, 12 Sep 2023 18:15:00 +0200 Subject: [PATCH 101/111] =?UTF-8?q?Improves=20variable=20names=20=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `t` → `exception` `w` → `watch` --- src/main/java/sirius/kernel/timer/Timers.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/sirius/kernel/timer/Timers.java b/src/main/java/sirius/kernel/timer/Timers.java index 68118296..f22480ff 100644 --- a/src/main/java/sirius/kernel/timer/Timers.java +++ b/src/main/java/sirius/kernel/timer/Timers.java @@ -141,8 +141,8 @@ public void run() { runOneHourTimers(); runEveryDayTimers(timeProvider.localTimeNow().getHour()); } - } catch (Exception t) { - Exceptions.handle(LOG, t); + } catch (Exception exception) { + Exceptions.handle(LOG, exception); } } } @@ -235,8 +235,8 @@ public void stopped() { } finally { timerLock.unlock(); } - } catch (Exception t) { - Exceptions.handle(LOG, t); + } catch (Exception exception) { + Exceptions.handle(LOG, exception); } } @@ -394,8 +394,8 @@ private void startTimer() { } finally { timerLock.unlock(); } - } catch (Exception t) { - Exceptions.handle(LOG, t); + } catch (Exception exception) { + Exceptions.handle(LOG, exception); } } @@ -410,14 +410,14 @@ private void executeTask(final TimedTask task) { .handle()) .start(() -> { try { - Watch w = Watch.start(); + Watch watch = Watch.start(); task.runTimer(); - if (w.elapsed(TimeUnit.SECONDS, false) > 1) { + if (watch.elapsed(TimeUnit.SECONDS, false) > 1) { LOG.WARN("TimedTask '%s' (%s) took over a second to complete! " + "Consider executing the work in a separate executor!", task, task.getClass()); } - } catch (Exception t) { - Exceptions.handle(LOG, t); + } catch (Exception exception) { + Exceptions.handle(LOG, exception); } }); } From e7b1c9bd1f9122c549d96628773d69cf8e1cda17 Mon Sep 17 00:00:00 2001 From: mko Date: Fri, 15 Sep 2023 15:36:03 +0200 Subject: [PATCH 102/111] Fixes a typo in the javadoc for Files#getFileExtension --- src/main/java/sirius/kernel/commons/Files.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/sirius/kernel/commons/Files.java b/src/main/java/sirius/kernel/commons/Files.java index 2a49fa5f..7fa7d782 100644 --- a/src/main/java/sirius/kernel/commons/Files.java +++ b/src/main/java/sirius/kernel/commons/Files.java @@ -66,7 +66,7 @@ public static Optional toSaneFileName(@Nullable String input) { * * @param path the path to parse * @return the file extension (txt for test.txt) without the leading dot. Returns null if - * the input is empty or does not contain a file extension. The a file contains several extensions, like + * the input is empty or does not contain a file extension. If a file contains several extensions, like * .txt.zip, only the last is returned. */ @Nullable From 79c768d5181caaeb5382bff12d3c762953d76fe7 Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Wed, 27 Sep 2023 08:44:30 +0200 Subject: [PATCH 103/111] Increases the maximum of parsed JSON strings The previous limit (5 mil) was a bit low for our use cases. The new limit shouldn't be a problem as newer versions of Jackson already use a default of 20 mil. Fixes: SIRI-883 --- src/main/java/sirius/kernel/commons/Json.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/sirius/kernel/commons/Json.java b/src/main/java/sirius/kernel/commons/Json.java index 79dd2b38..b62a9f90 100644 --- a/src/main/java/sirius/kernel/commons/Json.java +++ b/src/main/java/sirius/kernel/commons/Json.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonPointer; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.StreamReadConstraints; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -62,6 +63,10 @@ public class Json { .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .registerModule(new JavaTimeModule()); + static { + MAPPER.getFactory().setStreamReadConstraints(StreamReadConstraints.builder().maxStringLength(25_000_000).build()); + } + private Json() { } From 446df145a376efb74e41310deb3a786bc1fbaceb Mon Sep 17 00:00:00 2001 From: Sascha Bieberstein Date: Wed, 27 Sep 2023 10:05:01 +0200 Subject: [PATCH 104/111] Updates sirius parent from 10.2.0 to 10.2.1 Fixes: SIRI-883 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b452e4ed..c4010509 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.scireum sirius-parent - 10.2.0 + 10.2.1 sirius-kernel SIRIUS kernel From c3bf24b8bebdd249fcc23efcbc70c933526e5992 Mon Sep 17 00:00:00 2001 From: mko Date: Wed, 4 Oct 2023 13:20:10 +0200 Subject: [PATCH 105/111] Adds a new helper class for dates Fixes: OX-10394 --- src/main/java/sirius/kernel/commons/Dates.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/java/sirius/kernel/commons/Dates.java diff --git a/src/main/java/sirius/kernel/commons/Dates.java b/src/main/java/sirius/kernel/commons/Dates.java new file mode 100644 index 00000000..9189add5 --- /dev/null +++ b/src/main/java/sirius/kernel/commons/Dates.java @@ -0,0 +1,16 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons; + +/** + * Provides various helper methods for dealing with dates. + */ +public class Dates { + +} From f9286a0c62e4057bbe2441f67e4a81c5ea7aa86a Mon Sep 17 00:00:00 2001 From: mko Date: Wed, 4 Oct 2023 13:45:35 +0200 Subject: [PATCH 106/111] Adds methods (incl. Tests) which compute the latest date of the given dates Fixes: OX-10394 --- .../java/sirius/kernel/commons/Dates.java | 36 +++++++++++++++++++ .../kotlin/sirius/kernel/commons/DatesTest.kt | 36 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 src/test/kotlin/sirius/kernel/commons/DatesTest.kt diff --git a/src/main/java/sirius/kernel/commons/Dates.java b/src/main/java/sirius/kernel/commons/Dates.java index 9189add5..481fe09a 100644 --- a/src/main/java/sirius/kernel/commons/Dates.java +++ b/src/main/java/sirius/kernel/commons/Dates.java @@ -8,9 +8,45 @@ package sirius.kernel.commons; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Objects; + /** * Provides various helper methods for dealing with dates. */ public class Dates { + /** + * All methods are static, therefore no instances are required. + */ + private Dates() { + } + + /** + * Computes the latest date-time of the given date-time objects. + * + * @param dateTimes the date-time objects to compare + * @return the latest date-time object or null if no date is given + */ + public static LocalDateTime computeLatestDate(LocalDateTime... dateTimes) { + return Arrays.stream(dateTimes) + .filter(Objects::nonNull) + .max(LocalDateTime::compareTo) + .orElse(null); + } + + /** + * Computes the latest date of the given date objects. + * + * @param dates the dates to compare + * @return the latest date object or null if no date is given + */ + public static LocalDate computeLatestDate(LocalDate... dates) { + return Arrays.stream(dates) + .filter(Objects::nonNull) + .max(LocalDate::compareTo) + .orElse(null); + } } diff --git a/src/test/kotlin/sirius/kernel/commons/DatesTest.kt b/src/test/kotlin/sirius/kernel/commons/DatesTest.kt new file mode 100644 index 00000000..07dd1a08 --- /dev/null +++ b/src/test/kotlin/sirius/kernel/commons/DatesTest.kt @@ -0,0 +1,36 @@ +/* + * Made with all the love in the world + * by scireum in Remshalden, Germany + * + * Copyright by scireum GmbH + * http://www.scireum.de - info@scireum.de + */ + +package sirius.kernel.commons + +import java.time.LocalDate +import java.time.LocalDateTime +import kotlin.test.* + +/** + * Tests the [Dates] class. + */ +class DatesTest { + @Test + fun computeLatestDateTime() { + val theDayBeforeYesterday = LocalDateTime.now().minusDays(2) + val yesterday = LocalDateTime.now().minusDays(1) + val now = LocalDateTime.now() + assertEquals(now, Dates.computeLatestDate(null, theDayBeforeYesterday, now, yesterday)) + assertEquals(null, null) + } + + @Test + fun computeLatestDate() { + val theDayBeforeYesterday = LocalDate.now().minusDays(2) + val yesterday = LocalDate.now().minusDays(1) + val now = LocalDate.now() + assertEquals(now, Dates.computeLatestDate(null, theDayBeforeYesterday, now, yesterday)) + assertEquals(null, null) + } +} From 699c8826dd3055ae8e56c1986df486e30e1c6b47 Mon Sep 17 00:00:00 2001 From: mko Date: Wed, 4 Oct 2023 13:46:53 +0200 Subject: [PATCH 107/111] Adds methods (incl. Tests) which compute the earliest date of the given dates Fixes: OX-10394 --- .../java/sirius/kernel/commons/Dates.java | 26 +++++++++++++++++++ .../kotlin/sirius/kernel/commons/DatesTest.kt | 18 +++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/main/java/sirius/kernel/commons/Dates.java b/src/main/java/sirius/kernel/commons/Dates.java index 481fe09a..f039ab44 100644 --- a/src/main/java/sirius/kernel/commons/Dates.java +++ b/src/main/java/sirius/kernel/commons/Dates.java @@ -49,4 +49,30 @@ public static LocalDate computeLatestDate(LocalDate... dates) { .max(LocalDate::compareTo) .orElse(null); } + + /** + * Computes the earliest date-time of the given date-time objects. + * + * @param dateTimes the date-time objects to compare + * @return the earliest date-time object or null if no date is given + */ + public static LocalDateTime computeEarliestDate(LocalDateTime... dateTimes) { + return Arrays.stream(dateTimes) + .filter(Objects::nonNull) + .min(LocalDateTime::compareTo) + .orElse(null); + } + + /** + * Computes the earliest date of the given date objects. + * + * @param dates the dates to compare + * @return the earliest date object or null if no date is given + */ + public static LocalDate computeEarliestDate(LocalDate... dates) { + return Arrays.stream(dates) + .filter(Objects::nonNull) + .min(LocalDate::compareTo) + .orElse(null); + } } diff --git a/src/test/kotlin/sirius/kernel/commons/DatesTest.kt b/src/test/kotlin/sirius/kernel/commons/DatesTest.kt index 07dd1a08..6d251612 100644 --- a/src/test/kotlin/sirius/kernel/commons/DatesTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/DatesTest.kt @@ -33,4 +33,22 @@ class DatesTest { assertEquals(now, Dates.computeLatestDate(null, theDayBeforeYesterday, now, yesterday)) assertEquals(null, null) } + + @Test + fun computeEarliestDateTime() { + val theDayBeforeYesterday = LocalDateTime.now().minusDays(2) + val yesterday = LocalDateTime.now().minusDays(1) + val now = LocalDateTime.now() + assertEquals(theDayBeforeYesterday, Dates.computeEarliestDate(null, theDayBeforeYesterday, now, yesterday)) + assertEquals(null, null) + } + + @Test + fun computeEarliestDate() { + val theDayBeforeYesterday = LocalDate.now().minusDays(2) + val yesterday = LocalDate.now().minusDays(1) + val now = LocalDate.now() + assertEquals(theDayBeforeYesterday, Dates.computeEarliestDate(null, theDayBeforeYesterday, now, yesterday)) + assertEquals(null, null) + } } From 0c2e3468dca08c5a253f4fb2abc0a0a510b06173 Mon Sep 17 00:00:00 2001 From: mko Date: Wed, 4 Oct 2023 14:59:19 +0200 Subject: [PATCH 108/111] Adds a variant for each method which allows to specify a list instead of varargs due to the java specific type erasure during compilation it was necessary to rename the methods Fixes: SIRI-885 --- .../java/sirius/kernel/commons/Dates.java | 70 ++++++++++++++----- .../kotlin/sirius/kernel/commons/DatesTest.kt | 4 +- 2 files changed, 54 insertions(+), 20 deletions(-) diff --git a/src/main/java/sirius/kernel/commons/Dates.java b/src/main/java/sirius/kernel/commons/Dates.java index f039ab44..246a09f8 100644 --- a/src/main/java/sirius/kernel/commons/Dates.java +++ b/src/main/java/sirius/kernel/commons/Dates.java @@ -11,6 +11,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Arrays; +import java.util.List; import java.util.Objects; /** @@ -30,11 +31,21 @@ private Dates() { * @param dateTimes the date-time objects to compare * @return the latest date-time object or null if no date is given */ - public static LocalDateTime computeLatestDate(LocalDateTime... dateTimes) { - return Arrays.stream(dateTimes) - .filter(Objects::nonNull) - .max(LocalDateTime::compareTo) - .orElse(null); + public static LocalDateTime computeLatestDateTimes(LocalDateTime... dateTimes) { + return Dates.computeLatestDateTimes(Arrays.asList(dateTimes)); + } + + /** + * Computes the latest date-time of the given date-time objects. + * + * @param dateTimes a list of date-time objects to compare + * @return the latest date-time object or null if no date is given + */ + public static LocalDateTime computeLatestDateTimes(List dateTimes) { + return dateTimes.stream() + .filter(Objects::nonNull) + .max(LocalDateTime::compareTo) + .orElse(null); } /** @@ -44,10 +55,19 @@ public static LocalDateTime computeLatestDate(LocalDateTime... dateTimes) { * @return the latest date object or null if no date is given */ public static LocalDate computeLatestDate(LocalDate... dates) { - return Arrays.stream(dates) - .filter(Objects::nonNull) - .max(LocalDate::compareTo) - .orElse(null); + return computeLatestDate(Arrays.asList(dates)); + } + + /** + * Computes the latest date of the given date objects. + * @param dates a list of date objects to compare + * @return the latest date object or null if no date is given + */ + public static LocalDate computeLatestDate(List dates) { + return dates.stream() + .filter(Objects::nonNull) + .max(LocalDate::compareTo) + .orElse(null); } /** @@ -56,11 +76,21 @@ public static LocalDate computeLatestDate(LocalDate... dates) { * @param dateTimes the date-time objects to compare * @return the earliest date-time object or null if no date is given */ - public static LocalDateTime computeEarliestDate(LocalDateTime... dateTimes) { - return Arrays.stream(dateTimes) - .filter(Objects::nonNull) - .min(LocalDateTime::compareTo) - .orElse(null); + public static LocalDateTime computeEarliestDateTime(LocalDateTime... dateTimes) { + return computeEarliestDateTime(Arrays.asList(dateTimes)); + } + + /** + * Computes the earliest date-time of the given date-time objects. + * + * @param dateTimes a list of date-time objects to compare + * @return the earliest date-time object or null if no date is given + */ + public static LocalDateTime computeEarliestDateTime(List dateTimes) { + return dateTimes.stream() + .filter(Objects::nonNull) + .min(LocalDateTime::compareTo) + .orElse(null); } /** @@ -70,9 +100,13 @@ public static LocalDateTime computeEarliestDate(LocalDateTime... dateTimes) { * @return the earliest date object or null if no date is given */ public static LocalDate computeEarliestDate(LocalDate... dates) { - return Arrays.stream(dates) - .filter(Objects::nonNull) - .min(LocalDate::compareTo) - .orElse(null); + return computeEarliestDate(Arrays.asList(dates)); + } + + public static LocalDate computeEarliestDate(List dates) { + return dates.stream() + .filter(Objects::nonNull) + .min(LocalDate::compareTo) + .orElse(null); } } diff --git a/src/test/kotlin/sirius/kernel/commons/DatesTest.kt b/src/test/kotlin/sirius/kernel/commons/DatesTest.kt index 6d251612..e8c1fb8f 100644 --- a/src/test/kotlin/sirius/kernel/commons/DatesTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/DatesTest.kt @@ -21,7 +21,7 @@ class DatesTest { val theDayBeforeYesterday = LocalDateTime.now().minusDays(2) val yesterday = LocalDateTime.now().minusDays(1) val now = LocalDateTime.now() - assertEquals(now, Dates.computeLatestDate(null, theDayBeforeYesterday, now, yesterday)) + assertEquals(now, Dates.computeLatestDateTimes(null, theDayBeforeYesterday, now, yesterday)) assertEquals(null, null) } @@ -39,7 +39,7 @@ class DatesTest { val theDayBeforeYesterday = LocalDateTime.now().minusDays(2) val yesterday = LocalDateTime.now().minusDays(1) val now = LocalDateTime.now() - assertEquals(theDayBeforeYesterday, Dates.computeEarliestDate(null, theDayBeforeYesterday, now, yesterday)) + assertEquals(theDayBeforeYesterday, Dates.computeEarliestDateTime(null, theDayBeforeYesterday, now, yesterday)) assertEquals(null, null) } From 27ca368c3868fc1e4dcc0a11e992ead341c7e78e Mon Sep 17 00:00:00 2001 From: mko Date: Wed, 4 Oct 2023 15:01:03 +0200 Subject: [PATCH 109/111] Fixes typo Fixes: SIRI-885 --- src/main/java/sirius/kernel/commons/Dates.java | 6 +++--- src/test/kotlin/sirius/kernel/commons/DatesTest.kt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/sirius/kernel/commons/Dates.java b/src/main/java/sirius/kernel/commons/Dates.java index 246a09f8..82586de6 100644 --- a/src/main/java/sirius/kernel/commons/Dates.java +++ b/src/main/java/sirius/kernel/commons/Dates.java @@ -31,8 +31,8 @@ private Dates() { * @param dateTimes the date-time objects to compare * @return the latest date-time object or null if no date is given */ - public static LocalDateTime computeLatestDateTimes(LocalDateTime... dateTimes) { - return Dates.computeLatestDateTimes(Arrays.asList(dateTimes)); + public static LocalDateTime computeLatestDateTime(LocalDateTime... dateTimes) { + return Dates.computeLatestDateTime(Arrays.asList(dateTimes)); } /** @@ -41,7 +41,7 @@ public static LocalDateTime computeLatestDateTimes(LocalDateTime... dateTimes) { * @param dateTimes a list of date-time objects to compare * @return the latest date-time object or null if no date is given */ - public static LocalDateTime computeLatestDateTimes(List dateTimes) { + public static LocalDateTime computeLatestDateTime(List dateTimes) { return dateTimes.stream() .filter(Objects::nonNull) .max(LocalDateTime::compareTo) diff --git a/src/test/kotlin/sirius/kernel/commons/DatesTest.kt b/src/test/kotlin/sirius/kernel/commons/DatesTest.kt index e8c1fb8f..4be614dd 100644 --- a/src/test/kotlin/sirius/kernel/commons/DatesTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/DatesTest.kt @@ -21,7 +21,7 @@ class DatesTest { val theDayBeforeYesterday = LocalDateTime.now().minusDays(2) val yesterday = LocalDateTime.now().minusDays(1) val now = LocalDateTime.now() - assertEquals(now, Dates.computeLatestDateTimes(null, theDayBeforeYesterday, now, yesterday)) + assertEquals(now, Dates.computeLatestDateTime(null, theDayBeforeYesterday, now, yesterday)) assertEquals(null, null) } From 1a2c2707b70feece7470bc6e78240276c92aed04 Mon Sep 17 00:00:00 2001 From: mko Date: Wed, 4 Oct 2023 15:02:33 +0200 Subject: [PATCH 110/111] Fixes silly null == null test :D :D :D Fixes: SIRI-885 --- src/test/kotlin/sirius/kernel/commons/DatesTest.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/kotlin/sirius/kernel/commons/DatesTest.kt b/src/test/kotlin/sirius/kernel/commons/DatesTest.kt index 4be614dd..4851e2a7 100644 --- a/src/test/kotlin/sirius/kernel/commons/DatesTest.kt +++ b/src/test/kotlin/sirius/kernel/commons/DatesTest.kt @@ -22,7 +22,7 @@ class DatesTest { val yesterday = LocalDateTime.now().minusDays(1) val now = LocalDateTime.now() assertEquals(now, Dates.computeLatestDateTime(null, theDayBeforeYesterday, now, yesterday)) - assertEquals(null, null) + assertEquals(null, Dates.computeLatestDateTime(null, null, null)) } @Test @@ -31,7 +31,7 @@ class DatesTest { val yesterday = LocalDate.now().minusDays(1) val now = LocalDate.now() assertEquals(now, Dates.computeLatestDate(null, theDayBeforeYesterday, now, yesterday)) - assertEquals(null, null) + assertEquals(null, Dates.computeLatestDate(null, null, null)) } @Test @@ -40,7 +40,7 @@ class DatesTest { val yesterday = LocalDateTime.now().minusDays(1) val now = LocalDateTime.now() assertEquals(theDayBeforeYesterday, Dates.computeEarliestDateTime(null, theDayBeforeYesterday, now, yesterday)) - assertEquals(null, null) + assertEquals(null, Dates.computeEarliestDateTime(null, null, null)) } @Test @@ -49,6 +49,6 @@ class DatesTest { val yesterday = LocalDate.now().minusDays(1) val now = LocalDate.now() assertEquals(theDayBeforeYesterday, Dates.computeEarliestDate(null, theDayBeforeYesterday, now, yesterday)) - assertEquals(null, null) + assertEquals(null, Dates.computeEarliestDate(null, null, null)) } } From 5dc99cfb2d6777cb7f80faf1818de6f49c46e0b5 Mon Sep 17 00:00:00 2001 From: mko Date: Wed, 4 Oct 2023 19:45:29 +0200 Subject: [PATCH 111/111] Adds missing javadoc Fixes: SIRI-885 --- src/main/java/sirius/kernel/commons/Dates.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/sirius/kernel/commons/Dates.java b/src/main/java/sirius/kernel/commons/Dates.java index 82586de6..306d13b0 100644 --- a/src/main/java/sirius/kernel/commons/Dates.java +++ b/src/main/java/sirius/kernel/commons/Dates.java @@ -103,6 +103,12 @@ public static LocalDate computeEarliestDate(LocalDate... dates) { return computeEarliestDate(Arrays.asList(dates)); } + /** + * Computes the earliest date of the given date objects. + * + * @param dates a list of date objects to compare + * @return the earliest date object or null if no date is given + */ public static LocalDate computeEarliestDate(List dates) { return dates.stream() .filter(Objects::nonNull)