>> match(String text, Type... typeHints);
Pattern getRegexp();
diff --git a/java/src/main/java/io/cucumber/cucumberexpressions/ExpressionFactory.java b/java/src/main/java/io/cucumber/cucumberexpressions/ExpressionFactory.java
index e5ebb2e6f..399641250 100644
--- a/java/src/main/java/io/cucumber/cucumberexpressions/ExpressionFactory.java
+++ b/java/src/main/java/io/cucumber/cucumberexpressions/ExpressionFactory.java
@@ -2,7 +2,6 @@
import org.apiguardian.api.API;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
@@ -11,9 +10,9 @@
* using heuristics. This is particularly useful for languages that don't have a
* literal syntax for regular expressions. In Java, a regular expression has to be represented as a String.
*
- * A string that starts with `^` and/or ends with `$` (or written in script style, i.e. starting with `/`
- * and ending with `/`) is considered a regular expression.
- * Everything else is considered a Cucumber expression.
+ * A string that starts with `^` and/or ends with `$` (or written in script style, i.e. starting with `/`
+ * and ending with `/`) is considered a regular expression.
+ * Everything else is considered a Cucumber expression.
*/
@API(status = API.Status.STABLE)
public final class ExpressionFactory {
diff --git a/java/src/main/java/io/cucumber/cucumberexpressions/GeneratedExpression.java b/java/src/main/java/io/cucumber/cucumberexpressions/GeneratedExpression.java
index ec5113a6c..5e15ee221 100644
--- a/java/src/main/java/io/cucumber/cucumberexpressions/GeneratedExpression.java
+++ b/java/src/main/java/io/cucumber/cucumberexpressions/GeneratedExpression.java
@@ -1,6 +1,7 @@
package io.cucumber.cucumberexpressions;
import org.apiguardian.api.API;
+import org.jspecify.annotations.Nullable;
import java.text.Collator;
import java.util.ArrayList;
@@ -11,7 +12,7 @@
import java.util.Map;
@API(status = API.Status.STABLE)
-public class GeneratedExpression {
+public final class GeneratedExpression {
private static final Collator ENGLISH_COLLATOR = Collator.getInstance(Locale.ENGLISH);
private static final String[] JAVA_KEYWORDS = {
"abstract", "assert", "boolean", "break", "byte", "case",
@@ -34,8 +35,8 @@ public class GeneratedExpression {
this.parameterTypes = parameterTypes;
}
- private static boolean isJavaKeyword(String keyword) {
- return (Arrays.binarySearch(JAVA_KEYWORDS, keyword, ENGLISH_COLLATOR) >= 0);
+ private static boolean isJavaKeyword(@Nullable String keyword) {
+ return Arrays.binarySearch(JAVA_KEYWORDS, keyword, ENGLISH_COLLATOR) >= 0;
}
public String getSource() {
diff --git a/java/src/main/java/io/cucumber/cucumberexpressions/Group.java b/java/src/main/java/io/cucumber/cucumberexpressions/Group.java
index 9b61fc6fe..fcf287fc3 100644
--- a/java/src/main/java/io/cucumber/cucumberexpressions/Group.java
+++ b/java/src/main/java/io/cucumber/cucumberexpressions/Group.java
@@ -1,6 +1,7 @@
package io.cucumber.cucumberexpressions;
import org.apiguardian.api.API;
+import org.jspecify.annotations.Nullable;
import java.util.List;
import java.util.regex.Pattern;
@@ -12,19 +13,20 @@
import java.util.Collection;
@API(status = API.Status.STABLE)
-public class Group {
+public final class Group {
private final List children;
- private final String value;
+ private final @Nullable String value;
private final int start;
private final int end;
- Group(String value, int start, int end, List children) {
+ Group(@Nullable String value, int start, int end, List children) {
this.value = value;
this.start = start;
this.end = end;
this.children = children;
}
-
+
+ @Nullable
public String getValue() {
return value;
}
@@ -62,11 +64,9 @@ public static Collection parse(Pattern expression) {
private static List toGroups(List children) {
List list = new ArrayList<>();
- if (children != null) {
- for (GroupBuilder child : children) {
- list.add(new Group(child.getSource(), child.getStartIndex(), child.getEndIndex(),
- toGroups(child.getChildren())));
- }
+ for (GroupBuilder child : children) {
+ list.add(new Group(child.getSource(), child.getStartIndex(), child.getEndIndex(),
+ toGroups(child.getChildren())));
}
return list;
}
diff --git a/java/src/main/java/io/cucumber/cucumberexpressions/GroupBuilder.java b/java/src/main/java/io/cucumber/cucumberexpressions/GroupBuilder.java
index f5222f6f6..d24bb77f5 100644
--- a/java/src/main/java/io/cucumber/cucumberexpressions/GroupBuilder.java
+++ b/java/src/main/java/io/cucumber/cucumberexpressions/GroupBuilder.java
@@ -1,15 +1,19 @@
package io.cucumber.cucumberexpressions;
+import org.jspecify.annotations.Nullable;
+
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
+import static java.util.Objects.requireNonNull;
+
final class GroupBuilder {
private final List groupBuilders = new ArrayList<>();
private boolean capturing = true;
- private String source;
- private int startIndex;
+ private @Nullable String source;
+ private final int startIndex;
private int endIndex;
GroupBuilder(int startIndex) {
@@ -48,7 +52,7 @@ List getChildren() {
}
String getSource() {
- return source;
+ return requireNonNull(source);
}
void setSource(String source) {
diff --git a/java/src/main/java/io/cucumber/cucumberexpressions/KeyboardFriendlyDecimalFormatSymbols.java b/java/src/main/java/io/cucumber/cucumberexpressions/KeyboardFriendlyDecimalFormatSymbols.java
index 39b5e07b5..0de2bb29c 100644
--- a/java/src/main/java/io/cucumber/cucumberexpressions/KeyboardFriendlyDecimalFormatSymbols.java
+++ b/java/src/main/java/io/cucumber/cucumberexpressions/KeyboardFriendlyDecimalFormatSymbols.java
@@ -8,8 +8,12 @@
*
* Note quite complete, feel free to make a suggestion.
*/
-class KeyboardFriendlyDecimalFormatSymbols {
+final class KeyboardFriendlyDecimalFormatSymbols {
+ private KeyboardFriendlyDecimalFormatSymbols(){
+ // utility class
+ }
+
static DecimalFormatSymbols getInstance(Locale locale) {
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
diff --git a/java/src/main/java/io/cucumber/cucumberexpressions/Located.java b/java/src/main/java/io/cucumber/cucumberexpressions/Located.java
new file mode 100644
index 000000000..a5bf23ee6
--- /dev/null
+++ b/java/src/main/java/io/cucumber/cucumberexpressions/Located.java
@@ -0,0 +1,9 @@
+package io.cucumber.cucumberexpressions;
+
+interface Located {
+
+ int start();
+
+ int end();
+
+}
diff --git a/java/src/main/java/io/cucumber/cucumberexpressions/Node.java b/java/src/main/java/io/cucumber/cucumberexpressions/Node.java
new file mode 100644
index 000000000..d84f7c8ab
--- /dev/null
+++ b/java/src/main/java/io/cucumber/cucumberexpressions/Node.java
@@ -0,0 +1,149 @@
+package io.cucumber.cucumberexpressions;
+
+import org.apiguardian.api.API;
+import org.jspecify.annotations.Nullable;
+
+import java.util.List;
+import java.util.Objects;
+
+import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.joining;
+import static org.apiguardian.api.API.Status.EXPERIMENTAL;
+
+@API(since = "18.1", status = EXPERIMENTAL)
+public final class Node implements Located {
+
+ private final Type type;
+ private final @Nullable List nodes;
+ private final @Nullable String token;
+ private final int start;
+ private final int end;
+
+ Node(Type type, int start, int end, String token) {
+ this(type, start, end, null, requireNonNull(token));
+ }
+
+ Node(Type type, int start, int end, List nodes) {
+ this(type, start, end, requireNonNull(nodes), null);
+ }
+
+ private Node(Type type, int start, int end, @Nullable List nodes, @Nullable String token) {
+ this.type = requireNonNull(type);
+ this.nodes = nodes;
+ this.token = token;
+ this.start = start;
+ this.end = end;
+ }
+
+ public enum Type {
+ TEXT_NODE,
+ OPTIONAL_NODE,
+ ALTERNATION_NODE,
+ ALTERNATIVE_NODE,
+ PARAMETER_NODE,
+ EXPRESSION_NODE
+ }
+
+ @Override
+ public int start() {
+ return start;
+ }
+
+ @Override
+ public int end() {
+ return end;
+ }
+
+ /**
+ * Returns child nodes, {@code null} if a leaf-node
+ */
+ @Nullable
+ public List nodes() {
+ return nodes;
+ }
+
+ List requireNodes() {
+ return requireNonNull(nodes);
+ }
+
+ public Type type() {
+ return type;
+ }
+
+ /**
+ * Returns the text contained with in this node, {@code null} if not a leaf-node
+ */
+ @Nullable
+ public String token() {
+ return token;
+ }
+
+ String text() {
+ if (nodes == null)
+ return requireNonNull(token);
+
+ return nodes.stream()
+ .map(Node::text)
+ .collect(joining());
+ }
+
+ @Override
+ public String toString() {
+ return toString(0).toString();
+ }
+
+ private StringBuilder toString(int depth) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < depth; i++) {
+ sb.append(" ");
+ }
+ sb.append("{")
+ .append("\"type\": \"").append(type)
+ .append("\", \"start\": ")
+ .append(start)
+ .append(", \"end\": ")
+ .append(end);
+
+ if (token != null) {
+ sb.append(", \"token\": \"").append(token.replaceAll("\\\\", "\\\\\\\\")).append("\"");
+ }
+
+ if (nodes != null) {
+ sb.append(", \"nodes\": ");
+ if (!nodes.isEmpty()) {
+ StringBuilder padding = new StringBuilder();
+ for (int i = 0; i < depth; i++) {
+ padding.append(" ");
+ }
+ sb.append(nodes.stream()
+ .map(node -> node.toString(depth + 1))
+ .collect(joining(",\n", "[\n", "\n" + padding + "]")));
+
+ } else {
+ sb.append("[]");
+ }
+ }
+ sb.append("}");
+ return sb;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ Node node = (Node) o;
+ return start == node.start &&
+ end == node.end &&
+ type == node.type &&
+ Objects.equals(nodes, node.nodes) &&
+ Objects.equals(token, node.token);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, nodes, token, start, end);
+ }
+
+}
diff --git a/java/src/main/java/io/cucumber/cucumberexpressions/NumberParser.java b/java/src/main/java/io/cucumber/cucumberexpressions/NumberParser.java
index 5cdc120c7..704df8677 100644
--- a/java/src/main/java/io/cucumber/cucumberexpressions/NumberParser.java
+++ b/java/src/main/java/io/cucumber/cucumberexpressions/NumberParser.java
@@ -12,8 +12,7 @@ final class NumberParser {
NumberParser(Locale locale) {
numberFormat = DecimalFormat.getNumberInstance(locale);
- if (numberFormat instanceof DecimalFormat) {
- DecimalFormat decimalFormat = (DecimalFormat) numberFormat;
+ if (numberFormat instanceof DecimalFormat decimalFormat) {
decimalFormat.setParseBigDecimal(true);
DecimalFormatSymbols symbols = KeyboardFriendlyDecimalFormatSymbols.getInstance(locale);
decimalFormat.setDecimalFormatSymbols(symbols);
diff --git a/java/src/main/java/io/cucumber/cucumberexpressions/ParameterByTypeTransformer.java b/java/src/main/java/io/cucumber/cucumberexpressions/ParameterByTypeTransformer.java
index 0d076b751..4aa7c32d3 100644
--- a/java/src/main/java/io/cucumber/cucumberexpressions/ParameterByTypeTransformer.java
+++ b/java/src/main/java/io/cucumber/cucumberexpressions/ParameterByTypeTransformer.java
@@ -1,6 +1,7 @@
package io.cucumber.cucumberexpressions;
import org.apiguardian.api.API;
+import org.jspecify.annotations.Nullable;
import java.lang.reflect.Type;
@@ -13,5 +14,6 @@
@FunctionalInterface
public interface ParameterByTypeTransformer {
- Object transform(String fromValue, Type toValueType) throws Throwable;
+ @Nullable
+ Object transform(@Nullable String fromValue, Type toValueType) throws Throwable;
}
diff --git a/java/src/main/java/io/cucumber/cucumberexpressions/ParameterType.java b/java/src/main/java/io/cucumber/cucumberexpressions/ParameterType.java
index c3f834807..f1bcfbdf9 100644
--- a/java/src/main/java/io/cucumber/cucumberexpressions/ParameterType.java
+++ b/java/src/main/java/io/cucumber/cucumberexpressions/ParameterType.java
@@ -1,6 +1,7 @@
package io.cucumber.cucumberexpressions;
import org.apiguardian.api.API;
+import org.jspecify.annotations.Nullable;
import java.lang.reflect.Type;
import java.util.List;
@@ -8,10 +9,13 @@
import java.util.regex.Pattern;
import static java.util.Collections.singletonList;
+import static java.util.Objects.requireNonNull;
@API(status = API.Status.STABLE)
public final class ParameterType implements Comparable> {
- @SuppressWarnings("RegExpRedundantEscape") // Android can't parse unescaped braces
+
+ // Android can't parse unescaped braces
+ @SuppressWarnings("RegExpRedundantEscape")
private static final Pattern ILLEGAL_PARAMETER_NAME_PATTERN = Pattern.compile("([{}()\\\\/])");
private static final Pattern UNESCAPE_PATTERN = Pattern.compile("(\\\\([\\[$.|?*+\\]]))");
@@ -24,10 +28,11 @@ public final class ParameterType implements Comparable> {
private final boolean anonymous;
private final boolean useRegexpMatchAsStrongTypeHint;
- static void checkParameterTypeName(String name) {
+ static String requireValidParameterTypeName(String name) {
if (!isValidParameterTypeName(name)) {
throw CucumberExpressionException.createInvalidParameterTypeName(name);
}
+ return name;
}
static boolean isValidParameterTypeName(String name) {
@@ -37,17 +42,13 @@ static boolean isValidParameterTypeName(String name) {
}
static ParameterType