From 5481450c2d691a5cedc76c27356e96b710cebec4 Mon Sep 17 00:00:00 2001 From: Julien Kronegg Date: Thu, 5 Jan 2023 07:18:57 +0100 Subject: [PATCH 01/15] Update CucumberExpression.java --- .../CucumberExpression.java | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index dd5a8124f..5ca24093d 100644 --- a/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -24,7 +24,27 @@ @API(status = API.Status.STABLE) public final class CucumberExpression implements Expression { - private static final Pattern ESCAPE_PATTERN = Pattern.compile("[\\\\^\\[({$.|?*+})\\]]"); + /** + * List of characters to be escaped. + * The last char is '}' with index 125, so we need only 126 characters. + */ + private static final boolean[] CHAR_TO_ESCAPE = new boolean[126]; + static { + CHAR_TO_ESCAPE['^'] = true; + CHAR_TO_ESCAPE['$'] = true; + CHAR_TO_ESCAPE['['] = true; + CHAR_TO_ESCAPE[']'] = true; + CHAR_TO_ESCAPE['('] = true; + CHAR_TO_ESCAPE[')'] = true; + CHAR_TO_ESCAPE['{'] = true; + CHAR_TO_ESCAPE['}'] = true; + CHAR_TO_ESCAPE['.'] = true; + CHAR_TO_ESCAPE['|'] = true; + CHAR_TO_ESCAPE['?'] = true; + CHAR_TO_ESCAPE['*'] = true; + CHAR_TO_ESCAPE['+'] = true; + CHAR_TO_ESCAPE['\\'] = true; + } private final List> parameterTypes = new ArrayList<>(); private final String source; private final TreeRegexp treeRegexp; @@ -61,9 +81,18 @@ private String rewriteToRegex(Node node) { } private static String escapeRegex(String text) { - return ESCAPE_PATTERN.matcher(text).replaceAll("\\\\$0"); + int length = text.length(); + StringBuilder sb = new StringBuilder(length * 2); // prevent resizes + int maxChar = CHAR_TO_ESCAPE.length; + for (int i = 0; i < length; i++) { + char currentChar = text.charAt(i); + if (currentChar < maxChar && CHAR_TO_ESCAPE[currentChar]) { + sb.append('\\'); + } + sb.append(currentChar); + } + return sb.toString(); } - private String rewriteOptional(Node node) { assertNoParameters(node, astNode -> createParameterIsNotAllowedInOptional(astNode, source)); From 73a51e179d975e373a181cc796dafb38366cb091 Mon Sep 17 00:00:00 2001 From: Julien Kronegg Date: Thu, 5 Jan 2023 07:30:08 +0100 Subject: [PATCH 02/15] Update CucumberExpressionTest.java --- .../CucumberExpressionTest.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index d9554df54..0fb6e6343 100644 --- a/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -12,6 +12,8 @@ import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; @@ -192,4 +194,34 @@ private List match(String expr, String text, ParameterTypeRegistry parameterT return list; } } + + @Test + void escape_all_regexp_characters() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { + assertEquals("\\^\\$\\[\\]\\(\\)\\{\\}\\.\\|\\?\\*\\+\\\\", delegateEscapeRegex("^$[](){}.|?*+\\")); + } + + @Test + void escape_escaped_regexp_characters() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { + assertEquals("\\^\\$\\[\\]\\\\\\(\\\\\\)\\{\\}\\\\\\\\\\.\\|\\?\\*\\+", delegateEscapeRegex("^$[]\\(\\){}\\\\.|?*+")); + } + + + @Test + void do_not_escape_when_there_is_nothing_to_escape() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { + assertEquals("dummy", delegateEscapeRegex("dummy")); + } + + @Test + void escapeRegex_gives_no_error_for_unicode_characters() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { + assertEquals("🥒", delegateEscapeRegex("🥒")); + } + + private String delegateEscapeRegex(String expression) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + // this delegate is used to test the private method `escapeRegex(String)` + ParameterTypeRegistry context = new ParameterTypeRegistry(Locale.ENGLISH); + Method escapeRegex = CucumberExpression.class.getDeclaredMethod("escapeRegex", String.class); + escapeRegex.setAccessible(true); + return (String)escapeRegex.invoke(new CucumberExpression("", context), expression); + } + } From 531d380b50735c41026dee88690b3266d2ea5bd0 Mon Sep 17 00:00:00 2001 From: Julien Kronegg Date: Thu, 5 Jan 2023 07:39:49 +0100 Subject: [PATCH 03/15] Update CHANGELOG.md Added entry for performance improvement --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b791844cd..649610514 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Fixed +- [Java] Improve cucumber expression creation performance ([#200](https://github.com/cucumber/cucumber-expressions/issues/200)) + ## [16.1.1] - 2022-12-08 ### Fixed - [Java] Improve expression creation performance ([#187](https://github.com/cucumber/cucumber-expressions/pull/187), [#189](https://github.com/cucumber/cucumber-expressions/pull/189)) From a53b0e5f92abe993f9be4c0713201c1e14c267b0 Mon Sep 17 00:00:00 2001 From: Julien Kronegg Date: Thu, 5 Jan 2023 07:49:41 +0100 Subject: [PATCH 04/15] Update CHANGELOG.md Replace issue link by PR link. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 649610514..2b2bea27c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ### Fixed -- [Java] Improve cucumber expression creation performance ([#200](https://github.com/cucumber/cucumber-expressions/issues/200)) +- [Java] Improve cucumber expression creation performance ([#202](https://github.com/cucumber/cucumber-expressions/pull/202)) ## [16.1.1] - 2022-12-08 ### Fixed From f264cf2de85ac201d712ac4ead39d845fea9f5cf Mon Sep 17 00:00:00 2001 From: Julien Kronegg Date: Sat, 14 Jan 2023 10:52:09 +0100 Subject: [PATCH 05/15] Created private package class `RegexpUtils` Moved `escapeRegex` method to a private package class. Improved code performance. --- .../cucumberexpressions/RegexpUtils.java | 70 +++++++++++++++++++ .../cucumberexpressions/RegexpUtilsTest.java | 30 ++++++++ 2 files changed, 100 insertions(+) create mode 100644 java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtils.java create mode 100644 java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtilsTest.java diff --git a/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtils.java b/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtils.java new file mode 100644 index 000000000..71734f382 --- /dev/null +++ b/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtils.java @@ -0,0 +1,70 @@ +package io.cucumber.cucumberexpressions; + +class RegexpUtils { + /** + * List of characters to be escaped. + * The last char is '}' with index 125, so we need only 126 characters. + */ + private static final boolean[] CHAR_TO_ESCAPE = new boolean[126]; + static { + CHAR_TO_ESCAPE['^'] = true; + CHAR_TO_ESCAPE['$'] = true; + CHAR_TO_ESCAPE['['] = true; + CHAR_TO_ESCAPE[']'] = true; + CHAR_TO_ESCAPE['('] = true; + CHAR_TO_ESCAPE[')'] = true; + CHAR_TO_ESCAPE['{'] = true; + CHAR_TO_ESCAPE['}'] = true; + CHAR_TO_ESCAPE['.'] = true; + CHAR_TO_ESCAPE['|'] = true; + CHAR_TO_ESCAPE['?'] = true; + CHAR_TO_ESCAPE['*'] = true; + CHAR_TO_ESCAPE['+'] = true; + CHAR_TO_ESCAPE['\\'] = true; + } + + /** + * Escapes the regexp characters (the ones from "^$(){}[].+*?\") + * from the given text, so that they are not considered as regexp + * characters. + * @param text the non-null input text + * @return the input text with escaped regexp characters + */ + public static String escapeRegex(String text) { + /* + Note on performance: this code has been benchmarked for + escaping frequencies of 100%, 50%, 20%, 10%, 1%, 0.1%. + Amongst 4 other variants (including Pattern matching), + this variant is the faster on all escaping frequencies. + */ + int length = text.length(); + StringBuilder sb = null; // lazy initialization + int blocStart=0; + int maxChar = CHAR_TO_ESCAPE.length; + for (int i = 0; i < length; i++) { + char currentChar = text.charAt(i); + if (currentChar < maxChar && CHAR_TO_ESCAPE[currentChar]) { + if (sb == null) { + sb = new StringBuilder(length * 2); + } + if (i > blocStart) { + // flush previous block + sb.append(text, blocStart, i); + } + sb.append('\\'); + sb.append(currentChar); + blocStart=i+1; + } + } + if (sb != null) { + // finalizing character escaping + if (length > blocStart) { + // flush remaining characters + sb.append(text, blocStart, length); + } + return sb.toString(); + } + return text; + } + +} diff --git a/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtilsTest.java b/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtilsTest.java new file mode 100644 index 000000000..a09f11ca0 --- /dev/null +++ b/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtilsTest.java @@ -0,0 +1,30 @@ +package io.cucumber.cucumberexpressions; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class RegexpUtilsTest { + + @Test + void escape_all_regexp_characters() { + assertEquals("\\^\\$\\[\\]\\(\\)\\{\\}\\.\\|\\?\\*\\+\\\\", RegexpUtils.escapeRegex("^$[](){}.|?*+\\")); + } + + @Test + void escape_escaped_regexp_characters() { + assertEquals("\\^\\$\\[\\]\\\\\\(\\\\\\)\\{\\}\\\\\\\\\\.\\|\\?\\*\\+", RegexpUtils.escapeRegex("^$[]\\(\\){}\\\\.|?*+")); + } + + + @Test + void do_not_escape_when_there_is_nothing_to_escape() { + assertEquals("dummy", RegexpUtils.escapeRegex("dummy")); + } + + @Test + void escapeRegex_gives_no_error_for_unicode_characters() { + assertEquals("🥒", RegexpUtils.escapeRegex("🥒")); + } + +} From 20cc3229d2f7e4aa1e2ca5dd47a2a92e57cf1754 Mon Sep 17 00:00:00 2001 From: Julien Kronegg Date: Sat, 14 Jan 2023 10:56:42 +0100 Subject: [PATCH 06/15] Moved tests to `RegexpUtilsTest` --- .../CucumberExpressionTest.java | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 0fb6e6343..43d009b2b 100644 --- a/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -12,8 +12,6 @@ import java.io.IOException; import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; @@ -195,33 +193,4 @@ private List match(String expr, String text, ParameterTypeRegistry parameterT } } - @Test - void escape_all_regexp_characters() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { - assertEquals("\\^\\$\\[\\]\\(\\)\\{\\}\\.\\|\\?\\*\\+\\\\", delegateEscapeRegex("^$[](){}.|?*+\\")); - } - - @Test - void escape_escaped_regexp_characters() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { - assertEquals("\\^\\$\\[\\]\\\\\\(\\\\\\)\\{\\}\\\\\\\\\\.\\|\\?\\*\\+", delegateEscapeRegex("^$[]\\(\\){}\\\\.|?*+")); - } - - - @Test - void do_not_escape_when_there_is_nothing_to_escape() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { - assertEquals("dummy", delegateEscapeRegex("dummy")); - } - - @Test - void escapeRegex_gives_no_error_for_unicode_characters() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { - assertEquals("🥒", delegateEscapeRegex("🥒")); - } - - private String delegateEscapeRegex(String expression) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { - // this delegate is used to test the private method `escapeRegex(String)` - ParameterTypeRegistry context = new ParameterTypeRegistry(Locale.ENGLISH); - Method escapeRegex = CucumberExpression.class.getDeclaredMethod("escapeRegex", String.class); - escapeRegex.setAccessible(true); - return (String)escapeRegex.invoke(new CucumberExpression("", context), expression); - } - } From fb2e664701514d749603b23beeeda8178b03530e Mon Sep 17 00:00:00 2001 From: Julien Kronegg Date: Sat, 14 Jan 2023 10:57:57 +0100 Subject: [PATCH 07/15] Reverted to original code --- .../io/cucumber/cucumberexpressions/CucumberExpressionTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java b/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java index 43d009b2b..d9554df54 100644 --- a/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java +++ b/java/src/test/java/io/cucumber/cucumberexpressions/CucumberExpressionTest.java @@ -192,5 +192,4 @@ private List match(String expr, String text, ParameterTypeRegistry parameterT return list; } } - } From 7f5c1710166ddbc0c7a927bfa13183eb4e5b81e3 Mon Sep 17 00:00:00 2001 From: Julien Kronegg Date: Sat, 14 Jan 2023 11:00:13 +0100 Subject: [PATCH 08/15] Moved `escapeRegex` to `RegexpUtils` --- .../CucumberExpression.java | 37 +------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 5ca24093d..006db8a0b 100644 --- a/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -24,27 +24,6 @@ @API(status = API.Status.STABLE) public final class CucumberExpression implements Expression { - /** - * List of characters to be escaped. - * The last char is '}' with index 125, so we need only 126 characters. - */ - private static final boolean[] CHAR_TO_ESCAPE = new boolean[126]; - static { - CHAR_TO_ESCAPE['^'] = true; - CHAR_TO_ESCAPE['$'] = true; - CHAR_TO_ESCAPE['['] = true; - CHAR_TO_ESCAPE[']'] = true; - CHAR_TO_ESCAPE['('] = true; - CHAR_TO_ESCAPE[')'] = true; - CHAR_TO_ESCAPE['{'] = true; - CHAR_TO_ESCAPE['}'] = true; - CHAR_TO_ESCAPE['.'] = true; - CHAR_TO_ESCAPE['|'] = true; - CHAR_TO_ESCAPE['?'] = true; - CHAR_TO_ESCAPE['*'] = true; - CHAR_TO_ESCAPE['+'] = true; - CHAR_TO_ESCAPE['\\'] = true; - } private final List> parameterTypes = new ArrayList<>(); private final String source; private final TreeRegexp treeRegexp; @@ -63,7 +42,7 @@ public final class CucumberExpression implements Expression { private String rewriteToRegex(Node node) { switch (node.type()) { case TEXT_NODE: - return escapeRegex(node.text()); + return RegexpUtils.escapeRegex(node.text()); case OPTIONAL_NODE: return rewriteOptional(node); case ALTERNATION_NODE: @@ -80,20 +59,6 @@ private String rewriteToRegex(Node node) { } } - private static String escapeRegex(String text) { - int length = text.length(); - StringBuilder sb = new StringBuilder(length * 2); // prevent resizes - int maxChar = CHAR_TO_ESCAPE.length; - for (int i = 0; i < length; i++) { - char currentChar = text.charAt(i); - if (currentChar < maxChar && CHAR_TO_ESCAPE[currentChar]) { - sb.append('\\'); - } - sb.append(currentChar); - } - return sb.toString(); - } - private String rewriteOptional(Node node) { assertNoParameters(node, astNode -> createParameterIsNotAllowedInOptional(astNode, source)); assertNoOptionals(node, astNode -> createOptionalIsNotAllowedInOptional(astNode, source)); From b6f51b46bff95c1ad4cdd921e16fce9c6285af93 Mon Sep 17 00:00:00 2001 From: Julien Kronegg Date: Sat, 14 Jan 2023 11:05:02 +0100 Subject: [PATCH 09/15] Delete RegexpUtilsTest.java --- .../cucumberexpressions/RegexpUtilsTest.java | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtilsTest.java diff --git a/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtilsTest.java b/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtilsTest.java deleted file mode 100644 index a09f11ca0..000000000 --- a/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtilsTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.cucumber.cucumberexpressions; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class RegexpUtilsTest { - - @Test - void escape_all_regexp_characters() { - assertEquals("\\^\\$\\[\\]\\(\\)\\{\\}\\.\\|\\?\\*\\+\\\\", RegexpUtils.escapeRegex("^$[](){}.|?*+\\")); - } - - @Test - void escape_escaped_regexp_characters() { - assertEquals("\\^\\$\\[\\]\\\\\\(\\\\\\)\\{\\}\\\\\\\\\\.\\|\\?\\*\\+", RegexpUtils.escapeRegex("^$[]\\(\\){}\\\\.|?*+")); - } - - - @Test - void do_not_escape_when_there_is_nothing_to_escape() { - assertEquals("dummy", RegexpUtils.escapeRegex("dummy")); - } - - @Test - void escapeRegex_gives_no_error_for_unicode_characters() { - assertEquals("🥒", RegexpUtils.escapeRegex("🥒")); - } - -} From 68999a0e60161f19632a1b6c3c7f8f5ef461f7bc Mon Sep 17 00:00:00 2001 From: Julien Kronegg Date: Sat, 14 Jan 2023 11:05:53 +0100 Subject: [PATCH 10/15] Added tests for `RegexpUtils` --- .../cucumberexpressions/RegexpUtilsTest.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 java/src/test/java/io/cucumber/cucumberexpressions/RegexpUtilsTest.java diff --git a/java/src/test/java/io/cucumber/cucumberexpressions/RegexpUtilsTest.java b/java/src/test/java/io/cucumber/cucumberexpressions/RegexpUtilsTest.java new file mode 100644 index 000000000..a09f11ca0 --- /dev/null +++ b/java/src/test/java/io/cucumber/cucumberexpressions/RegexpUtilsTest.java @@ -0,0 +1,30 @@ +package io.cucumber.cucumberexpressions; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class RegexpUtilsTest { + + @Test + void escape_all_regexp_characters() { + assertEquals("\\^\\$\\[\\]\\(\\)\\{\\}\\.\\|\\?\\*\\+\\\\", RegexpUtils.escapeRegex("^$[](){}.|?*+\\")); + } + + @Test + void escape_escaped_regexp_characters() { + assertEquals("\\^\\$\\[\\]\\\\\\(\\\\\\)\\{\\}\\\\\\\\\\.\\|\\?\\*\\+", RegexpUtils.escapeRegex("^$[]\\(\\){}\\\\.|?*+")); + } + + + @Test + void do_not_escape_when_there_is_nothing_to_escape() { + assertEquals("dummy", RegexpUtils.escapeRegex("dummy")); + } + + @Test + void escapeRegex_gives_no_error_for_unicode_characters() { + assertEquals("🥒", RegexpUtils.escapeRegex("🥒")); + } + +} From 99dd95dd05e1968675c5910415c84249949c38b5 Mon Sep 17 00:00:00 2001 From: Julien Kronegg Date: Sat, 14 Jan 2023 23:38:34 +0100 Subject: [PATCH 11/15] Minor edits based on PR comments Reformatting and variable renaming. --- .../cucumber/cucumberexpressions/RegexpUtils.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtils.java b/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtils.java index 71734f382..250c35cf5 100644 --- a/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtils.java +++ b/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtils.java @@ -6,6 +6,7 @@ class RegexpUtils { * The last char is '}' with index 125, so we need only 126 characters. */ private static final boolean[] CHAR_TO_ESCAPE = new boolean[126]; + static { CHAR_TO_ESCAPE['^'] = true; CHAR_TO_ESCAPE['$'] = true; @@ -27,6 +28,7 @@ class RegexpUtils { * Escapes the regexp characters (the ones from "^$(){}[].+*?\") * from the given text, so that they are not considered as regexp * characters. + * * @param text the non-null input text * @return the input text with escaped regexp characters */ @@ -39,7 +41,7 @@ public static String escapeRegex(String text) { */ int length = text.length(); StringBuilder sb = null; // lazy initialization - int blocStart=0; + int blockStart = 0; int maxChar = CHAR_TO_ESCAPE.length; for (int i = 0; i < length; i++) { char currentChar = text.charAt(i); @@ -47,20 +49,20 @@ public static String escapeRegex(String text) { if (sb == null) { sb = new StringBuilder(length * 2); } - if (i > blocStart) { + if (i > blockStart) { // flush previous block - sb.append(text, blocStart, i); + sb.append(text, blockStart, i); } sb.append('\\'); sb.append(currentChar); - blocStart=i+1; + blockStart = i + 1; } } if (sb != null) { // finalizing character escaping - if (length > blocStart) { + if (length > blockStart) { // flush remaining characters - sb.append(text, blocStart, length); + sb.append(text, blockStart, length); } return sb.toString(); } From 7aef82c3ae977ef63e3b5c1c0d478f43967e2e99 Mon Sep 17 00:00:00 2001 From: Julien Kronegg Date: Sat, 14 Jan 2023 23:44:01 +0100 Subject: [PATCH 12/15] Added static import on `escapeRegex` --- .../io/cucumber/cucumberexpressions/CucumberExpression.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java b/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java index 006db8a0b..c0ea66155 100644 --- a/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java +++ b/java/src/main/java/io/cucumber/cucumberexpressions/CucumberExpression.java @@ -19,6 +19,7 @@ import static io.cucumber.cucumberexpressions.CucumberExpressionException.createOptionalMayNotBeEmpty; import static io.cucumber.cucumberexpressions.CucumberExpressionException.createParameterIsNotAllowedInOptional; import static io.cucumber.cucumberexpressions.ParameterType.isValidParameterTypeName; +import static io.cucumber.cucumberexpressions.RegexpUtils.escapeRegex; import static io.cucumber.cucumberexpressions.UndefinedParameterTypeException.createUndefinedParameterType; import static java.util.stream.Collectors.joining; @@ -42,7 +43,7 @@ public final class CucumberExpression implements Expression { private String rewriteToRegex(Node node) { switch (node.type()) { case TEXT_NODE: - return RegexpUtils.escapeRegex(node.text()); + return escapeRegex(node.text()); case OPTIONAL_NODE: return rewriteOptional(node); case ALTERNATION_NODE: From 6c1f3ec7e846cdf9505c03422457c1a6f0201d8a Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 15 Jan 2023 12:15:07 +0100 Subject: [PATCH 13/15] Update java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtils.java --- .../main/java/io/cucumber/cucumberexpressions/RegexpUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtils.java b/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtils.java index 250c35cf5..3d441b548 100644 --- a/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtils.java +++ b/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtils.java @@ -60,7 +60,7 @@ public static String escapeRegex(String text) { } if (sb != null) { // finalizing character escaping - if (length > blockStart) { + if (blockStart < length) { // flush remaining characters sb.append(text, blockStart, length); } From afc42c8c9cf71b7e15ea004994e5e949e489af7d Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 15 Jan 2023 12:15:25 +0100 Subject: [PATCH 14/15] Update java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtils.java --- .../main/java/io/cucumber/cucumberexpressions/RegexpUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtils.java b/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtils.java index 3d441b548..0c8c1767e 100644 --- a/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtils.java +++ b/java/src/main/java/io/cucumber/cucumberexpressions/RegexpUtils.java @@ -49,7 +49,7 @@ public static String escapeRegex(String text) { if (sb == null) { sb = new StringBuilder(length * 2); } - if (i > blockStart) { + if (blockStart < i) { // flush previous block sb.append(text, blockStart, i); } From b2febb71364d7b02d57e85ba5bd84b4b74e1389f Mon Sep 17 00:00:00 2001 From: "M.P. Korstanje" Date: Sun, 15 Jan 2023 13:02:10 +0100 Subject: [PATCH 15/15] Add coverage for flush --- .../cucumberexpressions/RegexpUtilsTest.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/java/src/test/java/io/cucumber/cucumberexpressions/RegexpUtilsTest.java b/java/src/test/java/io/cucumber/cucumberexpressions/RegexpUtilsTest.java index a09f11ca0..7edb02136 100644 --- a/java/src/test/java/io/cucumber/cucumberexpressions/RegexpUtilsTest.java +++ b/java/src/test/java/io/cucumber/cucumberexpressions/RegexpUtilsTest.java @@ -2,29 +2,35 @@ import org.junit.jupiter.api.Test; +import static io.cucumber.cucumberexpressions.RegexpUtils.escapeRegex; import static org.junit.jupiter.api.Assertions.assertEquals; class RegexpUtilsTest { + @Test + void escape_regex_characters(){ + assertEquals("hello \\$world", escapeRegex("hello $world")); + } + @Test void escape_all_regexp_characters() { - assertEquals("\\^\\$\\[\\]\\(\\)\\{\\}\\.\\|\\?\\*\\+\\\\", RegexpUtils.escapeRegex("^$[](){}.|?*+\\")); + assertEquals("\\^\\$\\[\\]\\(\\)\\{\\}\\.\\|\\?\\*\\+\\\\", escapeRegex("^$[](){}.|?*+\\")); } @Test void escape_escaped_regexp_characters() { - assertEquals("\\^\\$\\[\\]\\\\\\(\\\\\\)\\{\\}\\\\\\\\\\.\\|\\?\\*\\+", RegexpUtils.escapeRegex("^$[]\\(\\){}\\\\.|?*+")); + assertEquals("\\^\\$\\[\\]\\\\\\(\\\\\\)\\{\\}\\\\\\\\\\.\\|\\?\\*\\+", escapeRegex("^$[]\\(\\){}\\\\.|?*+")); } @Test void do_not_escape_when_there_is_nothing_to_escape() { - assertEquals("dummy", RegexpUtils.escapeRegex("dummy")); + assertEquals("hello world", escapeRegex("hello world")); } @Test - void escapeRegex_gives_no_error_for_unicode_characters() { - assertEquals("🥒", RegexpUtils.escapeRegex("🥒")); + void gives_no_error_for_unicode_characters() { + assertEquals("🥒", escapeRegex("🥒")); } }