diff --git a/.mvn/jvm.config b/.mvn/jvm.config
index 7fecadb38e..e465a14f8e 100644
--- a/.mvn/jvm.config
+++ b/.mvn/jvm.config
@@ -1 +1,11 @@
-Dfile.encoding=UTF-8
+--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
diff --git a/cucumber-bom/pom.xml b/cucumber-bom/pom.xml
index c6ac71b25e..90b241742c 100644
--- a/cucumber-bom/pom.xml
+++ b/cucumber-bom/pom.xml
@@ -13,19 +13,19 @@
Cucumber-JVM: Bill of Materials
- 12.0.0
- 18.0.1
- 0.3.0
- 36.1.0
- 22.0.0
- 0.10.0
- 30.1.0
- 2.4.1
- 14.6.0
- 8.0.0
- 0.2.0
- 0.7.0
- 0.1.0
+ 13.0.0-SNAPSHOT
+ 19.0.0-SNAPSHOT
+ 0.4.0-SNAPSHOT
+ 38.0.0-SNAPSHOT
+ 23.0.0-SNAPSHOT
+ 0.12.0-SNAPSHOT
+ 32.0.0-SNAPSHOT
+ 3.0.0-SNAPSHOT
+ 15.0.0-SNAPSHOT
+ 9.0.0-SNAPSHOT
+ 0.3.0-SNAPSHOT
+ 0.8.0-SNAPSHOT
+ 0.2.0-SNAPSHOT
diff --git a/cucumber-cdi2/src/main/java/io/cucumber/cdi2/package-info.java b/cucumber-cdi2/src/main/java/io/cucumber/cdi2/package-info.java
new file mode 100644
index 0000000000..69a0f5bbde
--- /dev/null
+++ b/cucumber-cdi2/src/main/java/io/cucumber/cdi2/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package io.cucumber.cdi2;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/cucumber-core/pom.xml b/cucumber-core/pom.xml
index 7967089963..49569d1d46 100644
--- a/cucumber-core/pom.xml
+++ b/cucumber-core/pom.xml
@@ -200,13 +200,6 @@
test
-
- org.hamcrest
- hamcrest
- ${hamcrest.version}
- test
-
-
org.skyscreamerjsonassert
diff --git a/cucumber-core/src/main/java/cucumber/api/cli/Main.java b/cucumber-core/src/main/java/cucumber/api/cli/Main.java
index ef0267ca6a..e0adb4e17d 100644
--- a/cucumber-core/src/main/java/cucumber/api/cli/Main.java
+++ b/cucumber-core/src/main/java/cucumber/api/cli/Main.java
@@ -7,10 +7,14 @@
* @deprecated use {@link io.cucumber.core.cli.Main} instead.
*/
@Deprecated
-public class Main {
+public final class Main {
private static final Logger log = LoggerFactory.getLogger(Main.class);
+ private Main() {
+ /* no-op */
+ }
+
public static void main(String[] argv) {
byte exitStatus = run(argv, Thread.currentThread().getContextClassLoader());
System.exit(exitStatus);
diff --git a/cucumber-core/src/main/java/cucumber/api/cli/package-info.java b/cucumber-core/src/main/java/cucumber/api/cli/package-info.java
new file mode 100644
index 0000000000..fe5f58ed50
--- /dev/null
+++ b/cucumber-core/src/main/java/cucumber/api/cli/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package cucumber.api.cli;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/cucumber-core/src/main/java/io/cucumber/core/api/TypeRegistry.java b/cucumber-core/src/main/java/io/cucumber/core/api/TypeRegistry.java
deleted file mode 100644
index b1122bb787..0000000000
--- a/cucumber-core/src/main/java/io/cucumber/core/api/TypeRegistry.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package io.cucumber.core.api;
-
-import io.cucumber.cucumberexpressions.ParameterByTypeTransformer;
-import io.cucumber.cucumberexpressions.ParameterType;
-import io.cucumber.datatable.DataTableType;
-import io.cucumber.datatable.TableCellByTypeTransformer;
-import io.cucumber.datatable.TableEntryByTypeTransformer;
-import io.cucumber.docstring.DocStringType;
-import org.apiguardian.api.API;
-import org.apiguardian.api.API.Status;
-
-/**
- * The type registry records defines parameter types, data table types and
- * docstring transformers.
- *
- * @deprecated use the dedicated type annotations to register data table and
- * parameter types instead
- */
-@API(status = Status.STABLE)
-@Deprecated
-public interface TypeRegistry {
-
- /**
- * Defines a new parameter type.
- *
- * @param parameterType The new parameter type.
- */
- void defineParameterType(ParameterType> parameterType);
-
- /**
- * Defines a new docstring type.
- *
- * @param docStringType The new docstring type.
- */
- void defineDocStringType(DocStringType docStringType);
-
- /**
- * Defines a new data table type.
- *
- * @param tableType The new table type.
- */
- void defineDataTableType(DataTableType tableType);
-
- /**
- * Set default transformer for parameters which are not defined by
- * {@code defineParameterType(ParameterType>))}
- *
- * @param defaultParameterByTypeTransformer default transformer
- */
- void setDefaultParameterTransformer(ParameterByTypeTransformer defaultParameterByTypeTransformer);
-
- /**
- * Set default transformer for entries which are not defined by
- * {@code defineDataTableType(new DataTableType(Class,TableEntryTransformer))}
- *
- * @param tableEntryByTypeTransformer default transformer
- */
- void setDefaultDataTableEntryTransformer(TableEntryByTypeTransformer tableEntryByTypeTransformer);
-
- /**
- * Set default transformer for cells which are not defined by
- * {@code defineDataTableType(new DataTableType(Class,TableEntryTransformer))}
- *
- * @param tableCellByTypeTransformer default transformer
- */
- void setDefaultDataTableCellTransformer(TableCellByTypeTransformer tableCellByTypeTransformer);
-
-}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/backend/Backend.java b/cucumber-core/src/main/java/io/cucumber/core/backend/Backend.java
index 676fd43cbc..a4787b2560 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/backend/Backend.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/backend/Backend.java
@@ -1,6 +1,7 @@
package io.cucumber.core.backend;
import org.apiguardian.api.API;
+import org.jspecify.annotations.Nullable;
import java.net.URI;
import java.util.List;
@@ -15,7 +16,9 @@ public interface Backend {
* @param glue Glue that provides the steps to be executed.
* @param gluePaths The locations for the glue to be loaded.
*/
- void loadGlue(Glue glue, List gluePaths);
+ default void loadGlue(Glue glue, List gluePaths) {
+
+ }
/**
* Invoked before a new scenario starts. Implementations should do any
@@ -23,13 +26,20 @@ public interface Backend {
* step definitions can be loaded here. These step definitions should
* implement {@link ScenarioScoped}
*/
- void buildWorld();
+ default void buildWorld() {
+
+ }
/**
* Invoked at the end of a scenario, after hooks
*/
- void disposeWorld();
+ default void disposeWorld() {
+
+ }
- Snippet getSnippet();
+ @Nullable
+ default Snippet getSnippet() {
+ return null;
+ }
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/backend/CucumberInvocationTargetException.java b/cucumber-core/src/main/java/io/cucumber/core/backend/CucumberInvocationTargetException.java
index 7cfd04de76..a31c235357 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/backend/CucumberInvocationTargetException.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/backend/CucumberInvocationTargetException.java
@@ -4,6 +4,8 @@
import java.lang.reflect.InvocationTargetException;
+import static java.util.Objects.requireNonNull;
+
/**
* Thrown when an exception was thrown by glue code. Not to be confused with
* {@link CucumberBackendException} which is thrown when the backend failed to
@@ -13,11 +15,12 @@
public final class CucumberInvocationTargetException extends RuntimeException {
private final Located located;
- private final InvocationTargetException invocationTargetException;
+ private final Throwable cause;
public CucumberInvocationTargetException(Located located, InvocationTargetException invocationTargetException) {
+ super(invocationTargetException.getCause());
this.located = located;
- this.invocationTargetException = invocationTargetException;
+ this.cause = requireNonNull(invocationTargetException.getCause());
}
/**
@@ -33,7 +36,7 @@ public Located getLocated() {
}
@Override
- public Throwable getCause() {
- return invocationTargetException.getCause();
+ public synchronized Throwable getCause() {
+ return cause;
}
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/backend/DefaultObjectFactory.java b/cucumber-core/src/main/java/io/cucumber/core/backend/DefaultObjectFactory.java
index c3e0320c60..4a48a7ba8b 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/backend/DefaultObjectFactory.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/backend/DefaultObjectFactory.java
@@ -27,24 +27,28 @@ public final class DefaultObjectFactory implements ObjectFactory {
private final Map, Object> instances = new HashMap<>();
+ @Override
public void start() {
// No-op
}
+ @Override
public void stop() {
instances.clear();
}
+ @Override
public boolean addClass(Class> clazz) {
return true;
}
+ @Override
public T getInstance(Class type) {
- T instance = type.cast(instances.get(type));
- if (instance == null) {
- instance = cacheNewInstance(type);
+ Object instance = instances.get(type);
+ if (instance != null) {
+ return type.cast(instance);
}
- return instance;
+ return cacheNewInstance(type);
}
private T cacheNewInstance(Class type) {
diff --git a/cucumber-core/src/main/java/io/cucumber/core/backend/HookDefinition.java b/cucumber-core/src/main/java/io/cucumber/core/backend/HookDefinition.java
index f1aed8f08d..999777d717 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/backend/HookDefinition.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/backend/HookDefinition.java
@@ -25,6 +25,6 @@ enum HookType {
BEFORE_STEP,
- AFTER_STEP;
+ AFTER_STEP
}
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/backend/Located.java b/cucumber-core/src/main/java/io/cucumber/core/backend/Located.java
index 57005ad9c4..0c06d0a7f2 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/backend/Located.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/backend/Located.java
@@ -8,9 +8,11 @@
public interface Located {
/**
+ * Return true if this matches the location. This is used to filter stack
+ * traces.
+ *
* @param stackTraceElement The location of the step.
- * @return Return true if this matches the location. This
- * is used to filter stack traces.
+ * @return Return true if this matches the location.
*/
boolean isDefinedAt(StackTraceElement stackTraceElement);
diff --git a/cucumber-core/src/main/java/io/cucumber/core/backend/Options.java b/cucumber-core/src/main/java/io/cucumber/core/backend/Options.java
index a3554484ec..afa856126f 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/backend/Options.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/backend/Options.java
@@ -1,7 +1,10 @@
package io.cucumber.core.backend;
+import org.jspecify.annotations.Nullable;
+
public interface Options {
+ @Nullable
Class extends ObjectFactory> getObjectFactoryClass();
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/backend/Snippet.java b/cucumber-core/src/main/java/io/cucumber/core/backend/Snippet.java
index 88392e4245..c5e92be826 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/backend/Snippet.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/backend/Snippet.java
@@ -12,30 +12,35 @@ public interface Snippet {
/**
* The language of the generated snippet.
- *
- * @see io.cucumber.messages.types.Snippet#getLanguage()
+ *
* @return the language of the generated snippet.
+ * @see io.cucumber.messages.types.Snippet#getLanguage()
*/
default Optional language() {
return Optional.empty();
}
/**
- * @return a {@link java.text.MessageFormat} template used to generate a
- * snippet. The template can access the following variables:
- *
- *
{0} : Step Keyword
- *
{1} : Value of {@link #escapePattern(String)}
- *
{2} : Function name
- *
{3} : Value of {@link #arguments(Map)}
- *
{4} : Regexp hint comment
- *
{5} : value of {@link #tableHint()} if the step has a
- * table
- *
+ * Returns a {@link java.text.MessageFormat} template used to generate a
+ * snippet.
+ *
+ * The template can access the following variables:
+ *
+ *
{0} : Step Keyword
+ *
{1} : Value of {@link #escapePattern(String)}
+ *
{2} : Function name
+ *
{3} : Value of {@link #arguments(Map)}
+ *
{4} : Regexp hint comment
+ *
{5} : value of {@link #tableHint()} if the step has a table
+ *
+ *
+ * @return a template used to generate a snippet.
*/
MessageFormat template();
/**
+ * Returns a hint about alternative ways to declare a table argument
+ *
* @return a hint about alternative ways to declare a table argument
*/
String tableHint();
@@ -51,6 +56,8 @@ default Optional language() {
String arguments(Map arguments);
/**
+ * Escape representation of the pattern, if escaping is necessary.
+ *
* @param pattern the computed pattern that will match an undefined step
* @return an escaped representation of the pattern, if escaping is
* necessary.
diff --git a/cucumber-core/src/main/java/io/cucumber/core/backend/StackTraceElementReference.java b/cucumber-core/src/main/java/io/cucumber/core/backend/StackTraceElementReference.java
index 06219e8778..267c04bb34 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/backend/StackTraceElementReference.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/backend/StackTraceElementReference.java
@@ -5,7 +5,7 @@
import static java.util.Objects.requireNonNull;
-public class StackTraceElementReference implements SourceReference {
+public final class StackTraceElementReference implements SourceReference {
private final String className;
private final String methodName;
diff --git a/cucumber-core/src/main/java/io/cucumber/core/backend/StepDefinition.java b/cucumber-core/src/main/java/io/cucumber/core/backend/StepDefinition.java
index e59cbe381b..4b60b200c7 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/backend/StepDefinition.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/backend/StepDefinition.java
@@ -19,13 +19,17 @@ public interface StepDefinition extends Located {
void execute(Object[] args) throws CucumberBackendException, CucumberInvocationTargetException;
/**
+ * Return parameter information.
+ *
* @return parameter information, may not return null
*/
List parameterInfos();
/**
- * @return the pattern associated with this instance. Used for error
- * reporting only.
+ * Return the pattern associated with this instance. Used for error
+ * reporting only.
+ *
+ * @return the pattern associated with this instance.
*/
String getPattern();
diff --git a/cucumber-core/src/main/java/io/cucumber/core/backend/TestCaseState.java b/cucumber-core/src/main/java/io/cucumber/core/backend/TestCaseState.java
index db6e3ae87a..605b370dbd 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/backend/TestCaseState.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/backend/TestCaseState.java
@@ -9,6 +9,8 @@
public interface TestCaseState {
/**
+ * Return tags of this scenario.
+ *
* @return tags of this scenario.
*/
Collection getSourceTagNames();
@@ -16,7 +18,7 @@ public interface TestCaseState {
/**
* Returns the current status of this test case.
*
- * The test case status is calculate as the most severe status of the
+ * The test case status is calculated as the most severe status of the
* executed steps in the testcase so far.
*
* @return the current status of this test case
@@ -24,7 +26,11 @@ public interface TestCaseState {
Status getStatus();
/**
- * @return true if and only if {@link #getStatus()} returns "failed"
+ * Returns true when the scenario has failed.
+ *
+ * This is implemented as {@code this.getStatus() == Status.FAILED}.
+ *
+ * @return true if the scenario has failed.
*/
boolean isFailed();
@@ -54,6 +60,8 @@ public interface TestCaseState {
void attach(byte[] data, String mediaType, String name);
/**
+ * Attach data to the report(s).
+ *
* @param data what to attach, for example html.
* @param mediaType what is the data?
* @param name attachment name
@@ -70,24 +78,33 @@ public interface TestCaseState {
void log(String text);
/**
+ * Returns the name of the Scenario
+ *
* @return the name of the Scenario
*/
String getName();
/**
+ * Returns the id of the Scenario.
+ *
* @return the id of the Scenario.
*/
String getId();
/**
+ * Returns the uri of the Scenario.
+ *
* @return the uri of the Scenario.
*/
URI getUri();
/**
- * @return the line in the feature file of the Scenario. If this is a
- * Scenario from Scenario Outlines this will return the line of the
- * example row in the Scenario Outline.
+ * Returns the line in the feature file of the Scenario.
+ *
+ * If this is a Scenario from Scenario Outlines this will return the line of
+ * the example row in the Scenario Outline.
+ *
+ * @return the line in the feature file of the Scenario.
*/
Integer getLine();
diff --git a/cucumber-core/src/main/java/io/cucumber/core/backend/package-info.java b/cucumber-core/src/main/java/io/cucumber/core/backend/package-info.java
new file mode 100644
index 0000000000..17cc474d10
--- /dev/null
+++ b/cucumber-core/src/main/java/io/cucumber/core/backend/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package io.cucumber.core.backend;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/cucumber-core/src/main/java/io/cucumber/core/cli/Main.java b/cucumber-core/src/main/java/io/cucumber/core/cli/Main.java
index 7f4c398932..0ef51599bf 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/cli/Main.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/cli/Main.java
@@ -24,7 +24,11 @@
* {@link CommandlineOptions}.
*/
@API(status = API.Status.STABLE)
-public class Main {
+public final class Main {
+
+ private Main() {
+ /* no-op */
+ }
public static void main(String... argv) {
byte exitStatus = run(argv, Thread.currentThread().getContextClassLoader());
diff --git a/cucumber-core/src/main/java/io/cucumber/core/cli/package-info.java b/cucumber-core/src/main/java/io/cucumber/core/cli/package-info.java
new file mode 100644
index 0000000000..dc79e3d780
--- /dev/null
+++ b/cucumber-core/src/main/java/io/cucumber/core/cli/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package io.cucumber.core.cli;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/cucumber-core/src/main/java/io/cucumber/core/eventbus/AbstractEventBus.java b/cucumber-core/src/main/java/io/cucumber/core/eventbus/AbstractEventBus.java
index ba6cee4558..42e35adfab 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/eventbus/AbstractEventBus.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/eventbus/AbstractEventBus.java
@@ -2,11 +2,21 @@
public abstract class AbstractEventBus extends AbstractEventPublisher implements EventBus {
+ /**
+ * Send all events.
+ *
+ * May be overridden, but must be called.
+ */
@Override
public void sendAll(Iterable queue) {
super.sendAll(queue);
}
+ /**
+ * Send a single event.
+ *
+ * May be overridden, but must be called.
+ */
@Override
public void send(T event) {
super.send(event);
diff --git a/cucumber-core/src/main/java/io/cucumber/core/eventbus/AbstractEventPublisher.java b/cucumber-core/src/main/java/io/cucumber/core/eventbus/AbstractEventPublisher.java
index 0d67fb5f6a..4dcdb7b078 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/eventbus/AbstractEventPublisher.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/eventbus/AbstractEventPublisher.java
@@ -31,12 +31,22 @@ public final void removeHandlerFor(Class eventType, EventHandler handl
}
}
+ /**
+ * Send all events.
+ *
+ * May be overridden, but must be called.
+ */
protected void sendAll(Iterable events) {
for (T event : events) {
send(event);
}
}
+ /**
+ * Send a single event.
+ *
+ * May be overridden, but must be called.
+ */
protected void send(T event) {
if (handlers.containsKey(Event.class) && event instanceof Event) {
for (EventHandler handler : handlers.get(Event.class)) {
diff --git a/cucumber-core/src/main/java/io/cucumber/core/eventbus/IncrementingUuidGenerator.java b/cucumber-core/src/main/java/io/cucumber/core/eventbus/IncrementingUuidGenerator.java
index e11d6d07cc..af2646c363 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/eventbus/IncrementingUuidGenerator.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/eventbus/IncrementingUuidGenerator.java
@@ -130,7 +130,8 @@ public UUID generateId() {
" capacity. Please generate using a new instance or use another " +
UuidGenerator.class.getSimpleName() + "implementation.");
}
- long leastSigBits = counterValue | 0x8000000000000000L; // set variant
+ // set variant
+ long leastSigBits = counterValue | 0x8000000000000000L;
return new UUID(msb, leastSigBits);
}
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/eventbus/Options.java b/cucumber-core/src/main/java/io/cucumber/core/eventbus/Options.java
index b14ef7a05e..fe31349620 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/eventbus/Options.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/eventbus/Options.java
@@ -1,7 +1,10 @@
package io.cucumber.core.eventbus;
+import org.jspecify.annotations.Nullable;
+
public interface Options {
+ @Nullable
Class extends UuidGenerator> getUuidGeneratorClass();
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/eventbus/RandomUuidGenerator.java b/cucumber-core/src/main/java/io/cucumber/core/eventbus/RandomUuidGenerator.java
index 76f34deb39..b9d9f90acd 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/eventbus/RandomUuidGenerator.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/eventbus/RandomUuidGenerator.java
@@ -6,7 +6,12 @@
* UUID generator based on random numbers. The generator is thread-safe and
* supports multi-jvm usage of Cucumber.
*/
-public class RandomUuidGenerator implements UuidGenerator {
+public final class RandomUuidGenerator implements UuidGenerator {
+
+ public RandomUuidGenerator() {
+ /* no-op */
+ }
+
@Override
public UUID generateId() {
return UUID.randomUUID();
diff --git a/cucumber-core/src/main/java/io/cucumber/core/eventbus/UuidGenerator.java b/cucumber-core/src/main/java/io/cucumber/core/eventbus/UuidGenerator.java
index b698bbf96b..167a0df420 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/eventbus/UuidGenerator.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/eventbus/UuidGenerator.java
@@ -10,8 +10,10 @@
*/
@API(status = API.Status.EXPERIMENTAL)
public interface UuidGenerator extends Supplier {
+
UUID generateId();
+ @Override
default UUID get() {
return generateId();
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/eventbus/package-info.java b/cucumber-core/src/main/java/io/cucumber/core/eventbus/package-info.java
new file mode 100644
index 0000000000..5bc0cb5209
--- /dev/null
+++ b/cucumber-core/src/main/java/io/cucumber/core/eventbus/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package io.cucumber.core.eventbus;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/cucumber-core/src/main/java/io/cucumber/core/exception/CucumberException.java b/cucumber-core/src/main/java/io/cucumber/core/exception/CucumberException.java
index d4eba6d062..124d46ed75 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/exception/CucumberException.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/exception/CucumberException.java
@@ -1,16 +1,18 @@
package io.cucumber.core.exception;
+import org.jspecify.annotations.Nullable;
+
public class CucumberException extends RuntimeException {
- public CucumberException(String message) {
+ public CucumberException(@Nullable String message) {
super(message);
}
- public CucumberException(String message, Throwable cause) {
+ public CucumberException(@Nullable String message, @Nullable Throwable cause) {
super(message, cause);
}
- public CucumberException(Throwable cause) {
+ public CucumberException(@Nullable Throwable cause) {
super(cause);
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/exception/package-info.java b/cucumber-core/src/main/java/io/cucumber/core/exception/package-info.java
new file mode 100644
index 0000000000..999d929df0
--- /dev/null
+++ b/cucumber-core/src/main/java/io/cucumber/core/exception/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package io.cucumber.core.exception;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/cucumber-core/src/main/java/io/cucumber/core/feature/FeatureIdentifier.java b/cucumber-core/src/main/java/io/cucumber/core/feature/FeatureIdentifier.java
index 84d7391f33..33b4f3c298 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/feature/FeatureIdentifier.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/feature/FeatureIdentifier.java
@@ -11,12 +11,12 @@
*
* @see FeatureWithLines
*/
-public class FeatureIdentifier {
+public final class FeatureIdentifier {
private static final String FEATURE_FILE_SUFFIX = ".feature";
private FeatureIdentifier() {
-
+ /* no-op */
}
public static URI parse(String featureIdentifier) {
diff --git a/cucumber-core/src/main/java/io/cucumber/core/feature/FeaturePath.java b/cucumber-core/src/main/java/io/cucumber/core/feature/FeaturePath.java
index 23302be33b..1c0a546754 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/feature/FeaturePath.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/feature/FeaturePath.java
@@ -27,7 +27,7 @@
* @see FeatureIdentifier
* @see FeatureWithLines
*/
-public class FeaturePath {
+public final class FeaturePath {
private FeaturePath() {
diff --git a/cucumber-core/src/main/java/io/cucumber/core/feature/FeatureWithLines.java b/cucumber-core/src/main/java/io/cucumber/core/feature/FeatureWithLines.java
index 4d69ba217e..318f18bfde 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/feature/FeatureWithLines.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/feature/FeatureWithLines.java
@@ -28,7 +28,7 @@
* a {@link FeatureIdentifier} followed by a sequence of line numbers each
* preceded by a colon.
*/
-public class FeatureWithLines implements Serializable {
+public final class FeatureWithLines implements Serializable {
private static final long serialVersionUID = 20190126L;
private static final Pattern FEATURE_WITH_LINES_FILE_FORMAT = Pattern.compile("(?m:^| |)(.*?\\.feature(?::\\d+)*)");
@@ -131,6 +131,7 @@ public boolean equals(Object o) {
return uri.equals(that.uri) && lines.equals(that.lines);
}
+ @Override
public String toString() {
StringBuilder builder = new StringBuilder(uri.toString());
for (Integer line : lines) {
diff --git a/cucumber-core/src/main/java/io/cucumber/core/feature/GluePath.java b/cucumber-core/src/main/java/io/cucumber/core/feature/GluePath.java
index 601fd9c41d..a6412dc0a9 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/feature/GluePath.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/feature/GluePath.java
@@ -32,7 +32,7 @@
*
* It is recommended to always use the package name form.
*/
-public class GluePath {
+public final class GluePath {
private static final Logger log = LoggerFactory.getLogger(GluePath.class);
@@ -118,13 +118,14 @@ private static void warnWhenWellKnownProjectSourceDirectory(String gluePath) {
classPathResource = classPathResource.substring(0, classPathResource.length() - 1);
}
String packageName = classPathResource.replaceAll("/", ".");
- String message = "" +
- "Consider replacing glue path '%s' with '%s'.\n'" +
- "\n" +
- "The current glue path points to a source directory in your project. However " +
- "cucumber looks for glue (i.e. step definitions) on the classpath. By using a " +
- "package name you can avoid this ambiguity.";
- return String.format(message, gluePath, packageName);
+ return """
+ Consider replacing glue path '%s' with '%s'.
+
+ The current glue path points to a source directory in your \
+ project. However cucumber looks for glue (i.e. step \
+ definitions) on the classpath. By using a package name you \
+ can avoid this ambiguity."""
+ .formatted(gluePath, packageName);
});
}
@@ -133,10 +134,11 @@ private static boolean isProbablyPackage(String gluePath) {
&& !gluePath.contains(RESOURCE_SEPARATOR_STRING);
}
+ @SuppressWarnings("UnnecessaryParentheses")
private static boolean isValidIdentifier(String schemeSpecificPart) {
- for (String part : schemeSpecificPart.split("/")) {
+ for (String part : schemeSpecificPart.split("/", 0)) {
for (int i = 0; i < part.length(); i++) {
- if (i == 0 && !isJavaIdentifierStart(part.charAt(i))
+ if ((i == 0 && !isJavaIdentifierStart(part.charAt(i)))
|| (i != 0 && !isJavaIdentifierPart(part.charAt(i)))) {
return false;
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/feature/package-info.java b/cucumber-core/src/main/java/io/cucumber/core/feature/package-info.java
new file mode 100644
index 0000000000..dcec86ab24
--- /dev/null
+++ b/cucumber-core/src/main/java/io/cucumber/core/feature/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package io.cucumber.core.feature;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/cucumber-core/src/main/java/io/cucumber/core/filter/package-info.java b/cucumber-core/src/main/java/io/cucumber/core/filter/package-info.java
new file mode 100644
index 0000000000..d9b6789037
--- /dev/null
+++ b/cucumber-core/src/main/java/io/cucumber/core/filter/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package io.cucumber.core.filter;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/cucumber-core/src/main/java/io/cucumber/core/logging/LoggerFactory.java b/cucumber-core/src/main/java/io/cucumber/core/logging/LoggerFactory.java
index 7dade586cb..6ce3a01089 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/logging/LoggerFactory.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/logging/LoggerFactory.java
@@ -1,5 +1,7 @@
package io.cucumber.core.logging;
+import org.jspecify.annotations.Nullable;
+
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.function.Supplier;
import java.util.logging.Level;
@@ -114,7 +116,7 @@ public void trace(Throwable throwable, Supplier message) {
log(Level.FINER, throwable, message);
}
- private void log(Level level, Throwable throwable, Supplier message) {
+ private void log(Level level, @Nullable Throwable throwable, Supplier message) {
boolean loggable = julLogger.isLoggable(level);
if (loggable || !listeners.isEmpty()) {
LogRecord logRecord = createLogRecord(level, throwable, message);
@@ -125,7 +127,7 @@ private void log(Level level, Throwable throwable, Supplier message) {
}
}
- private LogRecord createLogRecord(Level level, Throwable throwable, Supplier message) {
+ private LogRecord createLogRecord(Level level, @Nullable Throwable throwable, Supplier message) {
StackTraceElement[] stack = new Throwable().getStackTrace();
String sourceClassName = null;
String sourceMethodName = null;
@@ -133,7 +135,8 @@ private LogRecord createLogRecord(Level level, Throwable throwable, Supplier exitStatus() {
@@ -109,7 +111,7 @@ private RuntimeOptionsBuilder parse(List args) {
String nextArg = removeArgFor(arg, args);
exitCode = printI18nKeywords(nextArg);
return parsedOptions;
- } else if (arg.equals(I18N) || arg.equals(I18N_KEYWORDS)) {
+ } else if (arg.equals(I18N)) {
String nextArg = removeArgFor(arg, args);
exitCode = printI18n(nextArg);
return parsedOptions;
diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java b/cucumber-core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java
index 9ad7b1aaa7..e254ac2201 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java
@@ -8,6 +8,7 @@
import io.cucumber.core.snippets.SnippetType;
import io.cucumber.tagexpressions.TagExpressionException;
import io.cucumber.tagexpressions.TagExpressionParser;
+import org.jspecify.annotations.Nullable;
import java.util.regex.Pattern;
@@ -18,7 +19,7 @@ public final class CucumberOptionsAnnotationParser {
private boolean featuresSpecified = false;
private boolean overridingGlueSpecified = false;
- private OptionsProvider optionsProvider;
+ private @Nullable OptionsProvider optionsProvider;
public CucumberOptionsAnnotationParser withOptionsProvider(OptionsProvider optionsProvider) {
this.optionsProvider = optionsProvider;
@@ -128,7 +129,7 @@ private void addGlue(CucumberOptions options, RuntimeOptionsBuilder args) {
}
private void addFeatures(CucumberOptions options, RuntimeOptionsBuilder args) {
- if (options != null && options.features().length != 0) {
+ if (options.features().length != 0) {
for (String feature : options.features()) {
FeatureWithLinesOrRerunPath parsed = FeatureWithLinesOrRerunPath.parse(feature);
parsed.getFeaturesToRerun().ifPresent(args::addRerun);
@@ -181,6 +182,7 @@ private static String packageName(Class> clazz) {
public interface OptionsProvider {
+ @Nullable
CucumberOptions getOptions(Class> clazz);
}
@@ -207,8 +209,10 @@ public interface CucumberOptions {
SnippetType snippets();
+ @Nullable
Class extends ObjectFactory> objectFactory();
+ @Nullable
Class extends UuidGenerator> uuidGenerator();
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/CucumberProperties.java b/cucumber-core/src/main/java/io/cucumber/core/options/CucumberProperties.java
index 0da50f0cd1..dd395d3564 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/options/CucumberProperties.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/options/CucumberProperties.java
@@ -2,6 +2,7 @@
import io.cucumber.core.logging.Logger;
import io.cucumber.core.logging.LoggerFactory;
+import org.jspecify.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
@@ -71,10 +72,10 @@ public static Map fromSystemProperties() {
static class CucumberPropertiesMap extends AbstractMap {
- private final CucumberPropertiesMap parent;
+ private final @Nullable CucumberPropertiesMap parent;
private final Map delegate;
- CucumberPropertiesMap(CucumberPropertiesMap parent, Map delegate) {
+ CucumberPropertiesMap(@Nullable CucumberPropertiesMap parent, Map delegate) {
this.delegate = requireNonNull(delegate);
this.parent = parent;
}
@@ -95,20 +96,19 @@ private static CucumberPropertiesMap create(Properties p) {
}
@Override
- public String get(Object key) {
+ public @Nullable String get(Object key) {
String exactMatch = super.get(key);
if (exactMatch != null) {
return exactMatch;
}
- if (!(key instanceof String)) {
+ if (!(key instanceof String keyString)) {
return null;
}
// Support old skool
// Not all environments allow properties to contain dots or dashes.
// So we map the requested property to its underscore case variant.
- String keyString = (String) key;
String uppercase = keyString
.replace(".", "_")
diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/CucumberPropertiesParser.java b/cucumber-core/src/main/java/io/cucumber/core/options/CucumberPropertiesParser.java
index e2c490ecbd..cd6a797942 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/options/CucumberPropertiesParser.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/options/CucumberPropertiesParser.java
@@ -38,6 +38,10 @@ public final class CucumberPropertiesParser {
private static final Logger log = LoggerFactory.getLogger(CucumberPropertiesParser.class);
+ public CucumberPropertiesParser() {
+ /* no-op */
+ }
+
public RuntimeOptionsBuilder parse(Map properties) {
return parse(properties::get);
}
@@ -101,7 +105,7 @@ public RuntimeOptionsBuilder parse(CucumberPropertiesProvider properties) {
parse(properties,
OPTIONS_PROPERTY_NAME,
identity(),
- warnWhenCucumberOptionsIsUsed());
+ CucumberPropertiesParser::warnWhenCucumberOptionsIsUsed);
parseAll(properties,
PLUGIN_PROPERTY_NAME,
@@ -110,7 +114,8 @@ public RuntimeOptionsBuilder parse(CucumberPropertiesProvider properties) {
parse(properties,
PLUGIN_PUBLISH_TOKEN_PROPERTY_NAME,
- identity(), // No validation - validated on server
+ // No validation - validated on server
+ identity(),
builder::setPublishToken);
parse(properties,
@@ -136,10 +141,10 @@ public RuntimeOptionsBuilder parse(CucumberPropertiesProvider properties) {
return builder;
}
- private static Consumer warnWhenCucumberOptionsIsUsed() {
+ private static void warnWhenCucumberOptionsIsUsed(String commandLineOptions) {
// Quite a few old blogs still recommend the use of cucumber.options
// This should take care of recurring question involving this property.
- return commandLineOptions -> log.warn(() -> String.format("" +
+ log.warn(() -> String.format("" +
"Passing commandline options via the property '%s' is no longer supported. " +
"Please use individual properties instead. " +
"See the java doc on %s for details.",
diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/CucumberPropertiesProvider.java b/cucumber-core/src/main/java/io/cucumber/core/options/CucumberPropertiesProvider.java
index 24e5128cf9..71aea4ca41 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/options/CucumberPropertiesProvider.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/options/CucumberPropertiesProvider.java
@@ -1,7 +1,10 @@
package io.cucumber.core.options;
+import org.jspecify.annotations.Nullable;
+
@FunctionalInterface
public interface CucumberPropertiesProvider {
+ @Nullable
String get(String key);
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/CurlOption.java b/cucumber-core/src/main/java/io/cucumber/core/options/CurlOption.java
index 978fca0560..d6fc597cb6 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/options/CurlOption.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/options/CurlOption.java
@@ -2,7 +2,6 @@
import java.net.InetSocketAddress;
import java.net.Proxy;
-import java.net.Proxy.Type;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.AbstractMap.SimpleEntry;
@@ -11,6 +10,8 @@
import java.util.Map.Entry;
import static java.net.Proxy.NO_PROXY;
+import static java.net.Proxy.Type.HTTP;
+import static java.net.Proxy.Type.SOCKS;
import static java.util.Arrays.asList;
import static java.util.Objects.requireNonNull;
@@ -90,9 +91,9 @@ private static Proxy parseProxy(String arg) {
Proxy.Type type;
if (protocol.equalsIgnoreCase("http") || protocol.equalsIgnoreCase("https")) {
- type = Type.HTTP;
+ type = HTTP;
} else if (protocol.equalsIgnoreCase("socks")) {
- type = Type.SOCKS;
+ type = SOCKS;
} else {
throw new IllegalArgumentException("'" + arg + "' did not have a valid proxy protocol");
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/FeatureWithLinesOrRerunPath.java b/cucumber-core/src/main/java/io/cucumber/core/options/FeatureWithLinesOrRerunPath.java
index 9e3fd3d839..62f6a9943a 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/options/FeatureWithLinesOrRerunPath.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/options/FeatureWithLinesOrRerunPath.java
@@ -1,6 +1,7 @@
package io.cucumber.core.options;
import io.cucumber.core.feature.FeatureWithLines;
+import org.jspecify.annotations.Nullable;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -32,11 +33,11 @@
*/
class FeatureWithLinesOrRerunPath {
- private final FeatureWithLines featureWithLines;
- private final Collection featuresWithLinesToRerun;
+ private final @Nullable FeatureWithLines featureWithLines;
+ private final @Nullable Collection featuresWithLinesToRerun;
FeatureWithLinesOrRerunPath(
- FeatureWithLines featureWithLines, Collection featuresWithLinesToRerun
+ @Nullable FeatureWithLines featureWithLines, @Nullable Collection featuresWithLinesToRerun
) {
this.featureWithLines = featureWithLines;
this.featuresWithLinesToRerun = featuresWithLinesToRerun;
diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/PickleOrderParser.java b/cucumber-core/src/main/java/io/cucumber/core/options/PickleOrderParser.java
index 3c8c8a42b3..3c51efe2df 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/options/PickleOrderParser.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/options/PickleOrderParser.java
@@ -15,6 +15,10 @@ final class PickleOrderParser {
private static final Pattern RANDOM_AND_SEED_PATTERN = Pattern.compile("random(?::(\\d+))?");
+ private PickleOrderParser() {
+ /* no-op */
+ }
+
static PickleOrder parse(String argument) {
if ("reverse".equals(argument)) {
return StandardPickleOrders.reverseLexicalUriOrder();
@@ -34,7 +38,7 @@ static PickleOrder parse(String argument) {
if (seedString != null) {
seed = Long.parseLong(seedString);
} else {
- seed = Math.abs(new Random().nextLong());
+ seed = new Random().nextLong(Long.MAX_VALUE);
log.info(() -> "Using random scenario order. Seed: " + seed);
}
return StandardPickleOrders.random(seed);
diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/PluginOption.java b/cucumber-core/src/main/java/io/cucumber/core/options/PluginOption.java
index f95dbe0f10..b727466e6d 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/options/PluginOption.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/options/PluginOption.java
@@ -20,7 +20,7 @@
import io.cucumber.plugin.ConcurrentEventListener;
import io.cucumber.plugin.EventListener;
import io.cucumber.plugin.Plugin;
-import io.cucumber.plugin.SummaryPrinter;
+import org.jspecify.annotations.Nullable;
import java.util.HashMap;
import java.util.HashSet;
@@ -35,7 +35,7 @@
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;
-public class PluginOption implements Options.Plugin {
+public final class PluginOption implements Options.Plugin {
private static final Logger log = LoggerFactory.getLogger(PluginOption.class);
@@ -88,9 +88,9 @@ public class PluginOption implements Options.Plugin {
private final String pluginString;
private final Class extends Plugin> pluginClass;
- private final String argument;
+ private final @Nullable String argument;
- private PluginOption(String pluginString, Class extends Plugin> pluginClass, String argument) {
+ private PluginOption(String pluginString, Class extends Plugin> pluginClass, @Nullable String argument) {
this.pluginString = requireNonNull(pluginString);
this.pluginClass = requireNonNull(pluginClass);
this.argument = argument;
@@ -190,7 +190,7 @@ public Class extends Plugin> pluginClass() {
}
@Override
- public String argument() {
+ public @Nullable String argument() {
return argument;
}
@@ -204,10 +204,6 @@ boolean isEventListener() {
|| ConcurrentEventListener.class.isAssignableFrom(pluginClass);
}
- boolean isSummaryPrinter() {
- return SummaryPrinter.class.isAssignableFrom(pluginClass);
- }
-
@Override
public boolean equals(Object o) {
if (this == o)
diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/RerunPath.java b/cucumber-core/src/main/java/io/cucumber/core/options/RerunPath.java
index bf88121f56..e5cd7f25a6 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/options/RerunPath.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/options/RerunPath.java
@@ -17,7 +17,11 @@
* Either a path to a rerun file or a directory containing exclusively rerun
* files.
*/
-class RerunPath {
+final class RerunPath {
+
+ private RerunPath() {
+ /* no-op */
+ }
static Collection parse(Path rerunFileOrDirectory) {
return listRerunFiles(rerunFileOrDirectory).stream()
@@ -29,6 +33,7 @@ static Collection parse(Path rerunFileOrDirectory) {
private static Set listRerunFiles(Path path) {
class FileCollector extends SimpleFileVisitor {
final Set paths = new HashSet<>();
+
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (!Files.isDirectory(file)) {
diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/RuntimeOptions.java b/cucumber-core/src/main/java/io/cucumber/core/options/RuntimeOptions.java
index 795d74b946..6952b1133b 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/options/RuntimeOptions.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/options/RuntimeOptions.java
@@ -10,6 +10,7 @@
import io.cucumber.core.plugin.PublishFormatter;
import io.cucumber.core.snippets.SnippetType;
import io.cucumber.tagexpressions.Expression;
+import org.jspecify.annotations.Nullable;
import java.net.URI;
import java.util.ArrayList;
@@ -21,7 +22,6 @@
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Pattern;
-import java.util.stream.Collectors;
import static io.cucumber.core.resource.ClasspathSupport.rootPackageUri;
import static java.lang.Boolean.FALSE;
@@ -51,10 +51,10 @@ public final class RuntimeOptions implements
private int threads = 1;
private PickleOrder pickleOrder = StandardPickleOrders.lexicalUriOrder();
private int count = 0;
- private Class extends ObjectFactory> objectFactoryClass;
- private Class extends UuidGenerator> uuidGeneratorClass;
- private String publishToken;
- private Boolean publish;
+ private @Nullable Class extends ObjectFactory> objectFactoryClass;
+ private @Nullable Class extends UuidGenerator> uuidGeneratorClass;
+ private @Nullable String publishToken;
+ private @Nullable Boolean publish;
// Disable the banner advertising the hosted cucumber reports by default
// until the uncertainty around the projects future is resolved. It would
// not be proper to advertise a service that may be discontinued to new
@@ -133,6 +133,7 @@ public boolean isMonochrome() {
return monochrome;
}
+ @Override
public boolean isWip() {
return wip;
}
@@ -161,7 +162,7 @@ public SnippetType getSnippetType() {
}
@Override
- public Class extends ObjectFactory> getObjectFactoryClass() {
+ public @Nullable Class extends ObjectFactory> getObjectFactoryClass() {
return objectFactoryClass;
}
@@ -170,7 +171,7 @@ void setObjectFactoryClass(Class extends ObjectFactory> objectFactoryClass) {
}
@Override
- public Class extends UuidGenerator> getUuidGeneratorClass() {
+ public @Nullable Class extends UuidGenerator> getUuidGeneratorClass() {
return uuidGeneratorClass;
}
@@ -193,11 +194,11 @@ void setGlue(List parsedGlue) {
@Override
public List getFeaturePaths() {
- return unmodifiableList(featurePaths.stream()
+ return featurePaths.stream()
.map(FeatureWithLines::uri)
.sorted()
.distinct()
- .collect(Collectors.toList()));
+ .toList();
}
void setFeaturePaths(List featurePaths) {
diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java b/cucumber-core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java
index 58b6792bc5..46fd9404e0 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java
@@ -8,6 +8,7 @@
import io.cucumber.core.plugin.Options;
import io.cucumber.core.snippets.SnippetType;
import io.cucumber.tagexpressions.Expression;
+import org.jspecify.annotations.Nullable;
import java.net.URI;
import java.util.ArrayList;
@@ -23,23 +24,23 @@ public final class RuntimeOptionsBuilder {
private final List parsedFeaturePaths = new ArrayList<>();
private final List parsedGlue = new ArrayList<>();
private final List plugins = new ArrayList<>();
- private List parsedRerunPaths = null;
- private Integer parsedThreads = null;
- private Boolean parsedDryRun = null;
- private Boolean parsedMonochrome = null;
- private SnippetType parsedSnippetType = null;
- private Boolean parsedWip = null;
- private PickleOrder parsedPickleOrder = null;
- private Integer parsedCount = null;
- private Class extends ObjectFactory> parsedObjectFactoryClass = null;
- private Class extends UuidGenerator> parsedUuidGeneratorClass = null;
- private Boolean addDefaultSummaryPrinter = null;
+ private @Nullable List parsedRerunPaths = null;
+ private @Nullable Integer parsedThreads = null;
+ private @Nullable Boolean parsedDryRun = null;
+ private @Nullable Boolean parsedMonochrome = null;
+ private @Nullable SnippetType parsedSnippetType = null;
+ private @Nullable Boolean parsedWip = null;
+ private @Nullable PickleOrder parsedPickleOrder = null;
+ private @Nullable Integer parsedCount = null;
+ private @Nullable Class extends ObjectFactory> parsedObjectFactoryClass = null;
+ private @Nullable Class extends UuidGenerator> parsedUuidGeneratorClass = null;
+ private @Nullable Boolean addDefaultSummaryPrinter = null;
private boolean addDefaultGlueIfAbsent;
private boolean addDefaultFeaturePathIfAbsent;
- private String parsedPublishToken = null;
- private Boolean parsedPublish;
- private Boolean parsedPublishQuiet;
- private Boolean parsedEnablePublishPlugin;
+ private @Nullable String parsedPublishToken = null;
+ private @Nullable Boolean parsedPublish;
+ private @Nullable Boolean parsedPublishQuiet;
+ private @Nullable Boolean parsedEnablePublishPlugin;
public RuntimeOptionsBuilder addRerun(Collection featureWithLines) {
if (parsedRerunPaths == null) {
@@ -66,7 +67,7 @@ public RuntimeOptionsBuilder addNameFilter(Pattern pattern) {
public RuntimeOptionsBuilder addPluginName(String pluginSpecification) {
PluginOption pluginOption = PluginOption.parse(pluginSpecification);
- if (pluginOption.isEventListener() || pluginOption.isSummaryPrinter()) {
+ if (pluginOption.isEventListener()) {
plugins.add(pluginOption);
} else {
throw new CucumberException("Unrecognized plugin: " + pluginSpecification);
diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/ShellWords.java b/cucumber-core/src/main/java/io/cucumber/core/options/ShellWords.java
index aed166ee6b..253d585c26 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/options/ShellWords.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/options/ShellWords.java
@@ -5,11 +5,12 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-class ShellWords {
+final class ShellWords {
private static final Pattern SHELLWORDS_PATTERN = Pattern.compile("[^\\s'\"]+|[']([^']*)[']|[\"]([^\"]*)[\"]");
private ShellWords() {
+ /* no-op */
}
static List parse(String cmdline) {
diff --git a/cucumber-core/src/main/java/io/cucumber/core/options/package-info.java b/cucumber-core/src/main/java/io/cucumber/core/options/package-info.java
new file mode 100644
index 0000000000..3b757b67f1
--- /dev/null
+++ b/cucumber-core/src/main/java/io/cucumber/core/options/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package io.cucumber.core.options;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/cucumber-core/src/main/java/io/cucumber/core/order/package-info.java b/cucumber-core/src/main/java/io/cucumber/core/order/package-info.java
new file mode 100644
index 0000000000..9e73eed23d
--- /dev/null
+++ b/cucumber-core/src/main/java/io/cucumber/core/order/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package io.cucumber.core.order;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/Banner.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/Banner.java
index ed8a1fe45f..9cb4e6f193 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/Banner.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/Banner.java
@@ -11,38 +11,6 @@
final class Banner {
private final boolean monochrome;
-
- static final class Line {
- private final List spans;
-
- Line(Span... spans) {
- this.spans = asList(spans);
- }
-
- Line(String text, AnsiEscapes... escapes) {
- this(new Span(text, escapes));
- }
-
- int length() {
- return spans.stream().map(span -> span.text.length()).mapToInt(Integer::intValue).sum();
- }
- }
-
- static final class Span {
- private final String text;
- private final AnsiEscapes[] escapes;
-
- Span(String text) {
- this.text = text;
- this.escapes = new AnsiEscapes[0];
- }
-
- Span(String text, AnsiEscapes... escapes) {
- this.text = text;
- this.escapes = escapes;
- }
- }
-
private final PrintStream out;
Banner(PrintStream out, boolean monochrome) {
@@ -82,4 +50,36 @@ void print(List lines, AnsiEscapes... border) {
private String times(char c, int count) {
return new String(new char[count]).replace('\0', c);
}
+
+ static final class Line {
+ private final List spans;
+
+ Line(Span... spans) {
+ this.spans = asList(spans);
+ }
+
+ Line(String text, AnsiEscapes... escapes) {
+ this(new Span(text, escapes));
+ }
+
+ int length() {
+ return spans.stream().map(span -> span.text.length()).mapToInt(Integer::intValue).sum();
+ }
+ }
+
+ static final class Span {
+ private final String text;
+ private final AnsiEscapes[] escapes;
+
+ Span(String text) {
+ this.text = text;
+ this.escapes = new AnsiEscapes[0];
+ }
+
+ Span(String text, AnsiEscapes... escapes) {
+ this.text = text;
+ this.escapes = escapes;
+ }
+ }
+
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/CanonicalEventOrder.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/CanonicalEventOrder.java
index cc59646fbf..a9dad526d1 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/CanonicalEventOrder.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/CanonicalEventOrder.java
@@ -53,8 +53,8 @@ public int compare(Event a, Event b) {
.thenComparing(Event::getInstant);
private static int testCaseEvents(Event a, Event b) {
- if (a instanceof TestCaseEvent && b instanceof TestCaseEvent) {
- return testCaseOrder.compare((TestCaseEvent) a, (TestCaseEvent) b);
+ if (a instanceof TestCaseEvent testCaseEventA && b instanceof TestCaseEvent testCaseEventB) {
+ return testCaseOrder.compare(testCaseEventA, testCaseEventB);
}
return 0;
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/CanonicalOrderEventPublisher.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/CanonicalOrderEventPublisher.java
index 5e687f9604..ea496afcca 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/CanonicalOrderEventPublisher.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/CanonicalOrderEventPublisher.java
@@ -4,12 +4,12 @@
import io.cucumber.plugin.event.Event;
import io.cucumber.plugin.event.TestRunFinished;
-import java.util.LinkedList;
+import java.util.ArrayList;
import java.util.List;
final class CanonicalOrderEventPublisher extends AbstractEventPublisher {
- private final List queue = new LinkedList<>();
+ private final List queue = new ArrayList<>();
public void handle(final Event event) {
queue.add(event);
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/Format.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/Format.java
index db8dece806..91438d8b97 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/Format.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/Format.java
@@ -20,6 +20,7 @@ private Color(AnsiEscapes... escapes) {
this.escapes = escapes;
}
+ @Override
public String text(String text) {
StringBuilder sb = new StringBuilder();
for (AnsiEscapes escape : escapes) {
@@ -34,10 +35,10 @@ public String text(String text) {
}
- class Monochrome implements Format {
+ final class Monochrome implements Format {
private Monochrome() {
-
+ /* no-op */
}
@Override
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/Formats.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/Formats.java
deleted file mode 100644
index a1be2581bb..0000000000
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/Formats.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package io.cucumber.core.plugin;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import static io.cucumber.core.plugin.Format.color;
-
-interface Formats {
-
- Format get(String key);
-
- String up(int n);
-
- static Formats monochrome() {
- return new Monochrome();
- }
-
- static Formats ansi() {
- return new Ansi();
- }
-
- final class Monochrome implements Formats {
-
- private Monochrome() {
-
- }
-
- public Format get(String key) {
- return text -> text;
- }
-
- public String up(int n) {
- return "";
- }
-
- }
-
- final class Ansi implements Formats {
-
- private Ansi() {
-
- }
-
- private static final Map formats = new HashMap() {
- {
- // Never used, but avoids NPE in formatters.
- put("undefined", color(AnsiEscapes.YELLOW));
- put("undefined_arg", color(AnsiEscapes.YELLOW, AnsiEscapes.INTENSITY_BOLD));
- put("unused", color(AnsiEscapes.YELLOW));
- put("unused_arg", color(AnsiEscapes.YELLOW, AnsiEscapes.INTENSITY_BOLD));
- put("pending", color(AnsiEscapes.YELLOW));
- put("pending_arg", color(AnsiEscapes.YELLOW, AnsiEscapes.INTENSITY_BOLD));
- put("executing", color(AnsiEscapes.GREY));
- put("executing_arg", color(AnsiEscapes.GREY, AnsiEscapes.INTENSITY_BOLD));
- put("failed", color(AnsiEscapes.RED));
- put("failed_arg", color(AnsiEscapes.RED, AnsiEscapes.INTENSITY_BOLD));
- put("ambiguous", color(AnsiEscapes.RED));
- put("ambiguous_arg", color(AnsiEscapes.RED, AnsiEscapes.INTENSITY_BOLD));
- put("passed", color(AnsiEscapes.GREEN));
- put("passed_arg", color(AnsiEscapes.GREEN, AnsiEscapes.INTENSITY_BOLD));
- put("outline", color(AnsiEscapes.CYAN));
- put("outline_arg", color(AnsiEscapes.CYAN, AnsiEscapes.INTENSITY_BOLD));
- put("skipped", color(AnsiEscapes.CYAN));
- put("skipped_arg", color(AnsiEscapes.CYAN, AnsiEscapes.INTENSITY_BOLD));
- put("comment", color(AnsiEscapes.GREY));
- put("tag", color(AnsiEscapes.CYAN));
- put("output", color(AnsiEscapes.BLUE));
- }
- };
-
- public Format get(String key) {
- Format format = formats.get(key);
- if (format == null)
- throw new NullPointerException("No format for key " + key);
- return format;
- }
-
- public String up(int n) {
- return AnsiEscapes.up(n).toString();
- }
-
- }
-
-}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/HtmlFormatter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/HtmlFormatter.java
index 64ca508010..653038055e 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/HtmlFormatter.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/HtmlFormatter.java
@@ -12,9 +12,12 @@ public final class HtmlFormatter implements ConcurrentEventListener {
private final MessagesToHtmlWriter writer;
- @SuppressWarnings("WeakerAccess") // Used by PluginFactory
- public HtmlFormatter(OutputStream out) throws IOException {
- this.writer = new MessagesToHtmlWriter(out, Jackson.OBJECT_MAPPER::writeValue);
+ // Used by PluginFactory
+ @SuppressWarnings("WeakerAccess")
+ public HtmlFormatter(OutputStream out) {
+ this.writer = MessagesToHtmlWriter //
+ .builder(Jackson.OBJECT_MAPPER::writeValue) //
+ .build(out);
}
@Override
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java
index dba97e9619..7fc65c339d 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/JsonFormatter.java
@@ -10,15 +10,13 @@
import java.io.OutputStream;
import java.net.URI;
-import static io.cucumber.jsonformatter.MessagesToJsonWriter.builder;
-
public final class JsonFormatter implements ConcurrentEventListener {
private final MessagesToJsonWriter writer;
public JsonFormatter(OutputStream out) {
URI cwdUri = new File("").toPath().toUri();
- this.writer = builder(Jackson.OBJECT_MAPPER::writeValue)
+ this.writer = MessagesToJsonWriter.builder(Jackson.OBJECT_MAPPER::writeValue)
.relativizeAgainst(cwdUri)
.build(out);
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/NoPublishFormatter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/NoPublishFormatter.java
index b7e0d4ac52..ed79d98dc2 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/NoPublishFormatter.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/NoPublishFormatter.java
@@ -6,6 +6,7 @@
import io.cucumber.plugin.event.EventPublisher;
import java.io.PrintStream;
+import java.util.Locale;
import static io.cucumber.core.options.Constants.PLUGIN_PUBLISH_ENABLED_PROPERTY_NAME;
import static io.cucumber.core.options.Constants.PLUGIN_PUBLISH_QUIET_PROPERTY_NAME;
@@ -16,6 +17,8 @@ public final class NoPublishFormatter implements ConcurrentEventListener, ColorA
private final PrintStream out;
private boolean monochrome = false;
+ // Used in plugins
+ @SuppressWarnings("unused")
public NoPublishFormatter() {
this(System.err);
}
@@ -62,7 +65,7 @@ private void printBanner() {
new Banner.Span("true", AnsiEscapes.CYAN)),
new Banner.Line(
new Banner.Span("Environment variable: "),
- new Banner.Span(PLUGIN_PUBLISH_ENABLED_PROPERTY_NAME.toUpperCase().replace('.', '_'),
+ new Banner.Span(PLUGIN_PUBLISH_ENABLED_PROPERTY_NAME.toUpperCase(Locale.US).replace('.', '_'),
AnsiEscapes.CYAN),
new Banner.Span("="),
new Banner.Span("true", AnsiEscapes.CYAN)),
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/Options.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/Options.java
index c9ff227716..229e30ea29 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/Options.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/Options.java
@@ -1,5 +1,7 @@
package io.cucumber.core.plugin;
+import org.jspecify.annotations.Nullable;
+
public interface Options {
Iterable plugins();
@@ -12,6 +14,7 @@ interface Plugin {
Class extends io.cucumber.plugin.Plugin> pluginClass();
+ @Nullable
String argument();
String pluginString();
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/PluginFactory.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/PluginFactory.java
index 4a978c6669..78733dd51f 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/PluginFactory.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/PluginFactory.java
@@ -5,6 +5,7 @@
import io.cucumber.core.logging.LoggerFactory;
import io.cucumber.core.options.CurlOption;
import io.cucumber.plugin.Plugin;
+import org.jspecify.annotations.Nullable;
import java.io.File;
import java.io.FileNotFoundException;
@@ -49,9 +50,9 @@ public final class PluginFactory {
Appendable.class
};
- private String pluginUsingDefaultOut = null;
+ private @Nullable String pluginUsingDefaultOut = null;
- private PrintStream defaultOut = new PrintStream(System.out) {
+ private @Nullable PrintStream defaultOut = new PrintStream(System.out) {
@Override
public void close() {
// We have no intention to close System.out
@@ -66,10 +67,11 @@ Plugin create(Options.Plugin plugin) {
}
}
- private T instantiate(String pluginString, Class pluginClass, String argument)
+ private T instantiate(String pluginString, Class pluginClass, @Nullable String argument)
throws IOException, URISyntaxException {
Map, Constructor> singleArgConstructors = findSingleArgConstructors(pluginClass);
- if (argument == null) {// No argument passed
+ // No argument passed
+ if (argument == null) {
Constructor outputStreamConstructor = singleArgConstructors.get(OutputStream.class);
if (outputStreamConstructor != null) {
return newInstance(outputStreamConstructor, defaultOutOrFailIfAlreadyUsed(pluginString));
@@ -105,7 +107,8 @@ private Map, Constructor> findSingleArgConstructors(Class plu
for (Class> ctorArgClass : CTOR_PARAMETERS) {
try {
result.put(ctorArgClass, pluginClass.getConstructor(ctorArgClass));
- } catch (NoSuchMethodException ignore) {
+ } catch (NoSuchMethodException ignored) {
+ /* no-op */
}
}
return result;
@@ -137,7 +140,7 @@ private PrintStream defaultOutOrFailIfAlreadyUsed(String pluginString) {
}
}
- private Constructor findEmptyConstructor(Class pluginClass) {
+ private @Nullable Constructor findEmptyConstructor(Class pluginClass) {
try {
return pluginClass.getConstructor();
} catch (NoSuchMethodException ignore) {
@@ -160,11 +163,7 @@ private Object convert(String arg, Class> ctorArgClass, String pluginString, C
return arg;
}
if (ctorArgClass.equals(OutputStream.class)) {
- if (arg == null) {
- return defaultOutOrFailIfAlreadyUsed(pluginString);
- } else {
- return openStream(arg);
- }
+ return openStream(arg);
}
if (ctorArgClass.equals(Appendable.class)) {
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/Plugins.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/Plugins.java
index 7c13621240..7f9c52b877 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/Plugins.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/Plugins.java
@@ -4,9 +4,9 @@
import io.cucumber.plugin.ConcurrentEventListener;
import io.cucumber.plugin.EventListener;
import io.cucumber.plugin.Plugin;
-import io.cucumber.plugin.StrictAware;
import io.cucumber.plugin.event.Event;
import io.cucumber.plugin.event.EventPublisher;
+import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@@ -17,7 +17,7 @@ public final class Plugins {
private final PluginFactory pluginFactory;
private final Options pluginOptions;
private boolean pluginNamesInstantiated;
- private EventPublisher orderedEventPublisher;
+ private @Nullable EventPublisher orderedEventPublisher;
public Plugins(PluginFactory pluginFactory, Options pluginOptions) {
this.pluginFactory = pluginFactory;
@@ -40,23 +40,14 @@ private List createPlugins() {
private void addPlugin(List plugins, Plugin plugin) {
plugins.add(plugin);
setMonochromeOnColorAwarePlugins(plugin);
- setStrictOnStrictAwarePlugins(plugin);
}
private void setMonochromeOnColorAwarePlugins(Plugin plugin) {
- if (plugin instanceof ColorAware) {
- ColorAware colorAware = (ColorAware) plugin;
+ if (plugin instanceof ColorAware colorAware) {
colorAware.setMonochrome(pluginOptions.isMonochrome());
}
}
- private void setStrictOnStrictAwarePlugins(Plugin plugin) {
- if (plugin instanceof StrictAware) {
- StrictAware strictAware = (StrictAware) plugin;
- strictAware.setStrict(true);
- }
- }
-
public List getPlugins() {
return plugins;
}
@@ -67,21 +58,21 @@ public void addPlugin(Plugin plugin) {
public void setEventBusOnEventListenerPlugins(EventPublisher eventPublisher) {
for (Plugin plugin : plugins) {
- if (plugin instanceof ConcurrentEventListener) {
- ((ConcurrentEventListener) plugin).setEventPublisher(eventPublisher, false);
- } else if (plugin instanceof EventListener) {
- ((EventListener) plugin).setEventPublisher(eventPublisher);
+ if (plugin instanceof ConcurrentEventListener eventListener) {
+ eventListener.setEventPublisher(eventPublisher, false);
+ } else if (plugin instanceof EventListener eventListener) {
+ eventListener.setEventPublisher(eventPublisher);
}
}
}
public void setSerialEventBusOnEventListenerPlugins(EventPublisher eventPublisher) {
for (Plugin plugin : plugins) {
- if (plugin instanceof ConcurrentEventListener) {
- ((ConcurrentEventListener) plugin).setEventPublisher(eventPublisher, true);
- } else if (plugin instanceof EventListener) {
+ if (plugin instanceof ConcurrentEventListener eventListener) {
+ eventListener.setEventPublisher(eventPublisher, true);
+ } else if (plugin instanceof EventListener eventListener) {
EventPublisher orderedEventPublisher = getOrderedEventPublisher(eventPublisher);
- ((EventListener) plugin).setEventPublisher(orderedEventPublisher);
+ eventListener.setEventPublisher(orderedEventPublisher);
}
}
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/PublishFormatter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/PublishFormatter.java
index 466fd0db60..210dd36d8b 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/PublishFormatter.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/PublishFormatter.java
@@ -5,6 +5,7 @@
import io.cucumber.plugin.ColorAware;
import io.cucumber.plugin.ConcurrentEventListener;
import io.cucumber.plugin.event.EventPublisher;
+import org.jspecify.annotations.Nullable;
import java.io.IOException;
import java.util.Map;
@@ -15,7 +16,7 @@
public final class PublishFormatter implements ConcurrentEventListener, ColorAware {
/**
- * Where to publishes messages by default
+ * Where to publish messages by default
*/
public static final String DEFAULT_CUCUMBER_MESSAGE_STORE_URL = "https://messages.cucumber.io/api/reports -X GET";
@@ -45,7 +46,7 @@ public void setMonochrome(boolean monochrome) {
urlReporter.setMonochrome(monochrome);
}
- private static CurlOption createCurlOption(String token) {
+ private static CurlOption createCurlOption(@Nullable String token) {
// Note: This only includes properties from the environment and
// cucumber.properties. It does not include junit-platform.properties
// Fixing this requires an overhaul of the plugin system.
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/RerunFormatter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/RerunFormatter.java
index f9dc9ee416..2efa7cbac2 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/RerunFormatter.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/RerunFormatter.java
@@ -10,6 +10,7 @@
import io.cucumber.plugin.event.EventPublisher;
import io.cucumber.query.Query;
import io.cucumber.query.Repository;
+import org.jspecify.annotations.Nullable;
import java.io.File;
import java.io.OutputStream;
@@ -18,12 +19,13 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
+import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collector;
-import static io.cucumber.query.Repository.RepositoryFeature.INCLUDE_GHERKIN_DOCUMENTS;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
@@ -36,7 +38,6 @@ public final class RerunFormatter implements ConcurrentEventListener {
private final PrintWriter writer;
private final Repository repository = Repository.builder()
- .feature(INCLUDE_GHERKIN_DOCUMENTS, true)
.build();
private final Query query = new Query(repository);
@@ -77,24 +78,6 @@ public void setEventPublisher(EventPublisher publisher) {
});
}
- private static final class UriAndLine {
- private final String uri;
- private final Long line;
-
- private UriAndLine(String uri, Long line) {
- this.uri = uri;
- this.line = line;
- }
-
- public String getUri() {
- return uri;
- }
-
- public Long getLine() {
- return line;
- }
- }
-
private void finishReport() {
query.findAllTestCaseStarted().stream()
.filter(this::isNotPassingOrSkipped)
@@ -107,11 +90,11 @@ private void finishReport() {
writer.close();
}
- private void printUriWithLines(String uri, TreeSet lines) {
+ private void printUriWithLines(String uri, Set lines) {
writer.println(renderFeatureWithLines(uri, lines));
}
- private static Collector>> groupByUriAndThenCollectLines() {
+ private static Collector>> groupByUriAndThenCollectLines() {
return groupingBy(
UriAndLine::getUri,
// Sort URIs
@@ -122,10 +105,10 @@ private void printUriWithLines(String uri, TreeSet lines) {
toCollection(TreeSet::new)));
}
- private static StringBuilder renderFeatureWithLines(String uri, TreeSet lines) {
+ private static StringBuilder renderFeatureWithLines(String uri, Set lines) {
String path = relativize(URI.create(uri)).toString();
StringBuilder builder = new StringBuilder(path);
- for (Long line : lines) {
+ for (Integer line : lines) {
builder.append(':');
builder.append(line);
}
@@ -134,7 +117,7 @@ private static StringBuilder renderFeatureWithLines(String uri, TreeSet li
private UriAndLine createUriAndLine(Pickle pickle) {
String uri = pickle.getUri();
- Long line = query.findLocationOf(pickle).map(Location::getLine).orElse(null);
+ Integer line = pickle.getLocation().map(Location::getLine).orElse(null);
return new UriAndLine(uri, line);
}
@@ -146,4 +129,23 @@ private boolean isNotPassingOrSkipped(TestCaseStarted event) {
.isPresent();
}
+ private static final class UriAndLine {
+ private final String uri;
+ private final @Nullable Integer line;
+
+ private UriAndLine(String uri, @Nullable Integer line) {
+ this.uri = uri;
+ this.line = line;
+ }
+
+ String getUri() {
+ return uri;
+ }
+
+ @Nullable
+ Integer getLine() {
+ return line;
+ }
+ }
+
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/TeamCityPlugin.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/TeamCityPlugin.java
index 9a4102a8cb..545c69593c 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/TeamCityPlugin.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/TeamCityPlugin.java
@@ -18,12 +18,13 @@
* href=https://www.jetbrains.com/help/teamcity/service-messages.html>TeamCity
* - Service Messages
*/
-public class TeamCityPlugin implements ConcurrentEventListener {
+public final class TeamCityPlugin implements ConcurrentEventListener {
private final OutputStream out;
private MessagesToTeamCityWriter writer;
- @SuppressWarnings("unused") // Used by PluginFactory
+ // Used by PluginFactory
+ @SuppressWarnings("unused")
public TeamCityPlugin() {
// This plugin prints markers for Team City and IntelliJ IDEA that
// allows them to associate the output to specific test cases. Printing
@@ -39,6 +40,7 @@ public void close() {
TeamCityPlugin(OutputStream out) {
this.out = out;
+ this.writer = MessagesToTeamCityWriter.builder().build(out);
}
@Override
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java
deleted file mode 100644
index 19d13b8008..0000000000
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/TestSourcesModel.java
+++ /dev/null
@@ -1,262 +0,0 @@
-package io.cucumber.core.plugin;
-
-import io.cucumber.gherkin.GherkinParser;
-import io.cucumber.messages.types.Background;
-import io.cucumber.messages.types.Envelope;
-import io.cucumber.messages.types.Examples;
-import io.cucumber.messages.types.Feature;
-import io.cucumber.messages.types.FeatureChild;
-import io.cucumber.messages.types.GherkinDocument;
-import io.cucumber.messages.types.Rule;
-import io.cucumber.messages.types.RuleChild;
-import io.cucumber.messages.types.Scenario;
-import io.cucumber.messages.types.Source;
-import io.cucumber.messages.types.SourceMediaType;
-import io.cucumber.messages.types.Step;
-import io.cucumber.messages.types.TableRow;
-import io.cucumber.plugin.event.TestSourceRead;
-
-import java.io.File;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import java.util.regex.Pattern;
-import java.util.stream.Stream;
-
-/**
- * Internal class, still used by Serenity.
- *
- * @see cucumber/cucumber-jvm#3076
- * @deprecated for removal, use messages + query.
- */
-@SuppressWarnings("unused")
-@Deprecated
-final class TestSourcesModel {
-
- private final Map pathToReadEventMap = new HashMap<>();
- private final Map pathToAstMap = new HashMap<>();
- private final Map> pathToNodeMap = new HashMap<>();
-
- static Scenario getScenarioDefinition(AstNode astNode) {
- AstNode candidate = astNode;
- while (candidate != null && !(candidate.node instanceof Scenario)) {
- candidate = candidate.parent;
- }
- return candidate == null ? null : (Scenario) candidate.node;
- }
-
- static boolean isBackgroundStep(AstNode astNode) {
- return astNode.parent.node instanceof Background;
- }
-
- static String calculateId(AstNode astNode) {
- Object node = astNode.node;
- if (node instanceof Rule) {
- return calculateId(astNode.parent) + ";" + convertToId(((Rule) node).getName());
- }
- if (node instanceof Scenario) {
- return calculateId(astNode.parent) + ";" + convertToId(((Scenario) node).getName());
- }
- if (node instanceof ExamplesRowWrapperNode) {
- return calculateId(astNode.parent) + ";" + (((ExamplesRowWrapperNode) node).bodyRowIndex + 2);
- }
- if (node instanceof TableRow) {
- return calculateId(astNode.parent) + ";" + 1;
- }
- if (node instanceof Examples) {
- return calculateId(astNode.parent) + ";" + convertToId(((Examples) node).getName());
- }
- if (node instanceof Feature) {
- return convertToId(((Feature) node).getName());
- }
- return "";
- }
-
- private static final Pattern replacementPattern = Pattern.compile("[\\s'_,!]");
-
- static String convertToId(String name) {
- return replacementPattern.matcher(name).replaceAll("-").toLowerCase();
- }
-
- static URI relativize(URI uri) {
- if (!"file".equals(uri.getScheme())) {
- return uri;
- }
- if (!uri.isAbsolute()) {
- return uri;
- }
-
- try {
- URI root = new File("").toURI();
- URI relative = root.relativize(uri);
- // Scheme is lost by relativize
- return new URI("file", relative.getSchemeSpecificPart(), relative.getFragment());
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException(e.getMessage(), e);
- }
- }
-
- void addTestSourceReadEvent(URI path, TestSourceRead event) {
- pathToReadEventMap.put(path, event);
- }
-
- Feature getFeature(URI path) {
- if (!pathToAstMap.containsKey(path)) {
- parseGherkinSource(path);
- }
- if (pathToAstMap.containsKey(path)) {
- return pathToAstMap.get(path).getFeature().orElse(null);
- }
- return null;
- }
-
- private void parseGherkinSource(URI path) {
- if (!pathToReadEventMap.containsKey(path)) {
- return;
- }
- String source = pathToReadEventMap.get(path).getSource();
-
- GherkinParser parser = GherkinParser.builder()
- .build();
-
- Stream envelopes = parser.parse(
- Envelope.of(new Source(path.toString(), source, SourceMediaType.TEXT_X_CUCUMBER_GHERKIN_PLAIN)));
-
- // TODO: What about empty gherkin docs?
- GherkinDocument gherkinDocument = envelopes
- .map(Envelope::getGherkinDocument)
- .filter(Optional::isPresent)
- .map(Optional::get)
- .findFirst()
- .orElse(null);
-
- pathToAstMap.put(path, gherkinDocument);
- Map nodeMap = new HashMap<>();
- // TODO: What about gherkin docs with no features?
- Feature feature = gherkinDocument.getFeature().get();
- AstNode currentParent = new AstNode(feature, null);
- for (FeatureChild child : feature.getChildren()) {
- processFeatureDefinition(nodeMap, child, currentParent);
- }
- pathToNodeMap.put(path, nodeMap);
-
- }
-
- private void processFeatureDefinition(Map nodeMap, FeatureChild child, AstNode currentParent) {
- child.getBackground().ifPresent(background -> processBackgroundDefinition(nodeMap, background, currentParent));
- child.getScenario().ifPresent(scenario -> processScenarioDefinition(nodeMap, scenario, currentParent));
- child.getRule().ifPresent(rule -> {
- AstNode childNode = new AstNode(rule, currentParent);
- nodeMap.put(rule.getLocation().getLine(), childNode);
- rule.getChildren().forEach(ruleChild -> processRuleDefinition(nodeMap, ruleChild, childNode));
- });
- }
-
- private void processBackgroundDefinition(
- Map nodeMap, Background background, AstNode currentParent
- ) {
- AstNode childNode = new AstNode(background, currentParent);
- nodeMap.put(background.getLocation().getLine(), childNode);
- for (Step step : background.getSteps()) {
- nodeMap.put(step.getLocation().getLine(), new AstNode(step, childNode));
- }
- }
-
- private void processScenarioDefinition(Map nodeMap, Scenario child, AstNode currentParent) {
- AstNode childNode = new AstNode(child, currentParent);
- nodeMap.put(child.getLocation().getLine(), childNode);
- for (io.cucumber.messages.types.Step step : child.getSteps()) {
- nodeMap.put(step.getLocation().getLine(), new AstNode(step, childNode));
- }
- if (!child.getExamples().isEmpty()) {
- processScenarioOutlineExamples(nodeMap, child, childNode);
- }
- }
-
- private void processRuleDefinition(Map nodeMap, RuleChild child, AstNode currentParent) {
- child.getBackground().ifPresent(background -> processBackgroundDefinition(nodeMap, background, currentParent));
- child.getScenario().ifPresent(scenario -> processScenarioDefinition(nodeMap, scenario, currentParent));
- }
-
- private void processScenarioOutlineExamples(
- Map nodeMap, Scenario scenarioOutline, AstNode parent
- ) {
- for (Examples examples : scenarioOutline.getExamples()) {
- AstNode examplesNode = new AstNode(examples, parent);
- // TODO: Can tables without headers even exist?
- TableRow headerRow = examples.getTableHeader().get();
- AstNode headerNode = new AstNode(headerRow, examplesNode);
- nodeMap.put(headerRow.getLocation().getLine(), headerNode);
- for (int i = 0; i < examples.getTableBody().size(); ++i) {
- TableRow examplesRow = examples.getTableBody().get(i);
- Object rowNode = new ExamplesRowWrapperNode(examplesRow, i);
- AstNode expandedScenarioNode = new AstNode(rowNode, examplesNode);
- nodeMap.put(examplesRow.getLocation().getLine(), expandedScenarioNode);
- }
- }
- }
-
- AstNode getAstNode(URI path, int line) {
- if (!pathToNodeMap.containsKey(path)) {
- parseGherkinSource(path);
- }
- if (pathToNodeMap.containsKey(path)) {
- return pathToNodeMap.get(path).get((long) line);
- }
- return null;
- }
-
- boolean hasBackground(URI path, int line) {
- if (!pathToNodeMap.containsKey(path)) {
- parseGherkinSource(path);
- }
- if (pathToNodeMap.containsKey(path)) {
- AstNode astNode = pathToNodeMap.get(path).get((long) line);
- return getBackgroundForTestCase(astNode).isPresent();
- }
- return false;
- }
-
- static Optional getBackgroundForTestCase(AstNode astNode) {
- Feature feature = getFeatureForTestCase(astNode);
- return feature.getChildren()
- .stream()
- .map(FeatureChild::getBackground)
- .filter(Optional::isPresent)
- .map(Optional::get)
- .findFirst();
- }
-
- private static Feature getFeatureForTestCase(AstNode astNode) {
- while (astNode.parent != null) {
- astNode = astNode.parent;
- }
- return (Feature) astNode.node;
- }
-
- static class ExamplesRowWrapperNode {
-
- final int bodyRowIndex;
-
- ExamplesRowWrapperNode(Object examplesRow, int bodyRowIndex) {
- this.bodyRowIndex = bodyRowIndex;
- }
-
- }
-
- static class AstNode {
-
- final Object node;
- final AstNode parent;
-
- AstNode(Object node, AstNode parent) {
- this.node = node;
- this.parent = parent;
- }
-
- }
-
-}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/TimelineFormatter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/TimelineFormatter.java
index 40ff501507..615a3f3841 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/TimelineFormatter.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/TimelineFormatter.java
@@ -19,6 +19,7 @@
import io.cucumber.query.Lineage;
import io.cucumber.query.Query;
import io.cucumber.query.Repository;
+import org.jspecify.annotations.Nullable;
import java.io.BufferedWriter;
import java.io.File;
@@ -31,6 +32,7 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
@@ -74,9 +76,10 @@ public final class TimelineFormatter implements ConcurrentEventListener {
.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)
.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
- @SuppressWarnings({ "unused", "RedundantThrows" }) // Used by PluginFactory
+ // Used by PluginFactory
+ @SuppressWarnings({ "unused", "RedundantThrows", "ResultOfMethodCallIgnored" })
public TimelineFormatter(File reportDir) throws FileNotFoundException {
- boolean dontCare = reportDir.mkdirs();
+ reportDir.mkdirs();
if (!reportDir.isDirectory()) {
throw new CucumberException(String.format("The %s needs an existing directory. Not a directory: %s",
getClass().getName(), reportDir.getAbsolutePath()));
@@ -110,7 +113,7 @@ private void writeTimeLineReport() throws IOException {
.map(testCaseStarted -> createTestData(
testCaseFinished, //
testCaseStarted, //
- createTimeLineGroup(timeLineGroupsById) //
+ workerId -> timeLineGroupsById.computeIfAbsent(workerId, id -> new TimeLineGroup(id, id)) //
)))
.filter(Optional::isPresent)
.map(Optional::get)
@@ -123,22 +126,6 @@ private void writeTimeLineReport() throws IOException {
writeTimeLineReport(timeLineGroups, timeLineItems);
}
- private Function createTimeLineGroup(
- Map timeLineGroups
- ) {
-
- return workerId -> timeLineGroups.computeIfAbsent(workerId, createTimeLineGroup());
- }
-
- private Function createTimeLineGroup() {
- return workerId -> {
- TimeLineGroup timeLineGroup = new TimeLineGroup();
- timeLineGroup.setContent(workerId);
- timeLineGroup.setId(workerId);
- return timeLineGroup;
- };
- }
-
private TimeLineItem createTestData(
TestCaseFinished testCaseFinished, TestCaseStarted testCaseStarted,
Function timeLineGroupCreator
@@ -190,7 +177,7 @@ private String getTagsValue(TestCaseStarted testCaseStarted) {
.map(pickle -> {
StringBuilder tags = new StringBuilder();
for (PickleTag tag : pickle.getTags()) {
- tags.append(tag.getName().toLowerCase()).append(",");
+ tags.append(tag.getName().toLowerCase(Locale.US)).append(",");
}
return tags.toString();
}).orElse("");
@@ -227,9 +214,6 @@ private void appendAsJsonToJs(
}
private void copyReportFiles() throws IOException {
- if (reportDir == null) {
- return;
- }
File outputDir = new File(reportDir.getPath());
for (String textAsset : TEXT_ASSETS) {
try (InputStream textAssetStream = getClass().getResourceAsStream(textAsset)) {
@@ -254,14 +238,11 @@ private static void copyFile(InputStream source, File dest) throws IOException {
static class TimeLineGroup {
- private String id;
- private String content;
+ private final String id;
+ private final String content;
- public void setId(String id) {
+ TimeLineGroup(String id, String content) {
this.id = id;
- }
-
- public void setContent(String content) {
this.content = content;
}
@@ -269,7 +250,7 @@ public String getId() {
return id;
}
- public String getContent() {
+ public @Nullable String getContent() {
return content;
}
@@ -277,15 +258,16 @@ public String getContent() {
static class TimeLineItem {
- private String id;
- private String feature;
- private String scenario;
+ private @Nullable String id;
+ private @Nullable String feature;
+ private @Nullable String scenario;
private long start;
- private String group;
- private String content = ""; // Replaced in JS file
- private String tags;
+ private @Nullable String group;
+ // Replaced in JS file
+ private String content = "";
+ private @Nullable String tags;
private long end;
- private String className;
+ private @Nullable String className;
public void setId(String id) {
this.id = id;
@@ -323,15 +305,15 @@ public void setClassName(String className) {
this.className = className;
}
- public String getId() {
+ public @Nullable String getId() {
return id;
}
- public String getFeature() {
+ public @Nullable String getFeature() {
return feature;
}
- public String getScenario() {
+ public @Nullable String getScenario() {
return scenario;
}
@@ -339,15 +321,15 @@ public long getStart() {
return start;
}
- public String getGroup() {
+ public @Nullable String getGroup() {
return group;
}
- public String getContent() {
+ public @Nullable String getContent() {
return content;
}
- public String getTags() {
+ public @Nullable String getTags() {
return tags;
}
@@ -355,7 +337,7 @@ public long getEnd() {
return end;
}
- public String getClassName() {
+ public @Nullable String getClassName() {
return className;
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/UTF8PrintWriter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/UTF8PrintWriter.java
index 096fbfcb06..a202446599 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/UTF8PrintWriter.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/UTF8PrintWriter.java
@@ -35,6 +35,7 @@ public void println(String s) {
}
}
+ @Override
public void flush() {
try {
out.flush();
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/UnusedStepsSummaryPrinter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/UnusedStepsSummaryPrinter.java
index 7f348f8931..a4998b01fd 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/UnusedStepsSummaryPrinter.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/UnusedStepsSummaryPrinter.java
@@ -18,10 +18,10 @@ public final class UnusedStepsSummaryPrinter implements ColorAware, ConcurrentEv
private final MessagesToUsageWriter writer;
- @SuppressWarnings("WeakerAccess") // Used by PluginFactory
+ // Used by PluginFactory
+ @SuppressWarnings("WeakerAccess")
public UnusedStepsSummaryPrinter(OutputStream out) {
- this.writer = MessagesToUsageWriter.builder(new UnusedReportSerializer())
- .build(out);
+ this.writer = MessagesToUsageWriter.builder(new UnusedReportSerializer()).build(out);
}
@Override
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/UrlOutputStream.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/UrlOutputStream.java
index 5a93fa965c..d78e31522f 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/UrlOutputStream.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/UrlOutputStream.java
@@ -2,6 +2,7 @@
import io.cucumber.core.options.CurlOption;
import io.cucumber.core.options.CurlOption.HttpMethod;
+import org.jspecify.annotations.Nullable;
import java.io.BufferedReader;
import java.io.IOException;
@@ -26,13 +27,13 @@
class UrlOutputStream extends OutputStream {
- private final UrlReporter urlReporter;
+ private final @Nullable UrlReporter urlReporter;
private final CurlOption option;
private final Path temp;
private final OutputStream tempOutputStream;
- UrlOutputStream(CurlOption option, UrlReporter urlReporter) throws IOException {
+ UrlOutputStream(CurlOption option, @Nullable UrlReporter urlReporter) throws IOException {
this.option = requireNonNull(option);
this.urlReporter = urlReporter;
this.temp = Files.createTempFile("cucumber", null);
@@ -145,7 +146,7 @@ private static IOException createCurlLikeException(
Map> requestHeaders,
Map> responseHeaders,
String responseBody,
- Exception e
+ @Nullable Exception e
) {
return new IOException(String.format(
"%s:\n> %s %s%s%s%s",
@@ -157,7 +158,7 @@ private static IOException createCurlLikeException(
responseBody), e);
}
- private static String headersToString(String prefix, Map> headers) {
+ private static String headersToString(String prefix, Map<@Nullable String, List<@Nullable String>> headers) {
return headers
.entrySet()
.stream()
@@ -167,7 +168,7 @@ private static String headersToString(String prefix, Map> h
.map(value -> {
if (header.getKey() == null) {
return prefix + value;
- } else if (header.getValue() == null) {
+ } else if (value == null) {
return prefix + header.getKey();
} else {
return prefix + header.getKey() + ": " + value;
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/UrlReporter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/UrlReporter.java
index 04515654ef..aef25ee290 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/UrlReporter.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/UrlReporter.java
@@ -8,11 +8,11 @@ final class UrlReporter implements ColorAware {
private final PrintStream out;
private boolean monochrome;
- public UrlReporter(PrintStream out) {
+ UrlReporter(PrintStream out) {
this.out = out;
}
- public void report(String message) {
+ void report(String message) {
if (monochrome) {
message = message.replaceAll("\u001B\\[[;\\d]*m", "");
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/UsageJsonFormatter.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/UsageJsonFormatter.java
index cc030ff3b9..b818ed19a8 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/plugin/UsageJsonFormatter.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/UsageJsonFormatter.java
@@ -16,7 +16,8 @@ public final class UsageJsonFormatter implements Plugin, ConcurrentEventListener
private final MessagesToUsageWriter writer;
- @SuppressWarnings("WeakerAccess") // Used by PluginFactory
+ // Used by PluginFactory
+ @SuppressWarnings("WeakerAccess")
public UsageJsonFormatter(OutputStream out) {
this.writer = MessagesToUsageWriter.builder(Jackson.OBJECT_MAPPER::writeValue)
.build(out);
diff --git a/cucumber-core/src/main/java/io/cucumber/core/plugin/package-info.java b/cucumber-core/src/main/java/io/cucumber/core/plugin/package-info.java
new file mode 100644
index 0000000000..174565c969
--- /dev/null
+++ b/cucumber-core/src/main/java/io/cucumber/core/plugin/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package io.cucumber.core.plugin;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/cucumber-core/src/main/java/io/cucumber/core/resource/ClasspathScanner.java b/cucumber-core/src/main/java/io/cucumber/core/resource/ClasspathScanner.java
index 6d8c1ffe11..3ac7f3ef31 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/resource/ClasspathScanner.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/resource/ClasspathScanner.java
@@ -27,7 +27,6 @@ public final class ClasspathScanner {
private static final String CLASS_FILE_SUFFIX = ".class";
private static final String PACKAGE_INFO_FILE_NAME = "package-info" + CLASS_FILE_SUFFIX;
private static final String MODULE_INFO_FILE_NAME = "module-info" + CLASS_FILE_SUFFIX;
- private static final Predicate> NULL_FILTER = aClass -> true;
private final PathScanner pathScanner = new PathScanner();
@@ -112,7 +111,7 @@ private Optional> safelyLoadClass(String fqn) {
}
public List> scanForClassesInPackage(String packageName) {
- return scanForClassesInPackage(packageName, NULL_FILTER);
+ return scanForClassesInPackage(packageName, aClass -> true);
}
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/resource/ClasspathSupport.java b/cucumber-core/src/main/java/io/cucumber/core/resource/ClasspathSupport.java
index 42ade80f36..6ce808999c 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/resource/ClasspathSupport.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/resource/ClasspathSupport.java
@@ -11,11 +11,11 @@
import java.util.Enumeration;
import java.util.List;
import java.util.regex.Pattern;
+import java.util.stream.Stream;
import static java.util.Arrays.stream;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;
-import static java.util.stream.Stream.of;
public final class ClasspathSupport {
@@ -69,8 +69,9 @@ public static String resourceNameOfPackageName(String packageName) {
static String determinePackageName(Path baseDir, String basePackageName, Path classFile) {
String subPackageName = determineSubpackageName(baseDir, classFile);
- return of(basePackageName, subPackageName)
- .filter(value -> !value.isEmpty()) // default package
+ return Stream.of(basePackageName, subPackageName)
+ // default package
+ .filter(value -> !value.isEmpty())
.collect(joining(PACKAGE_SEPARATOR_STRING));
}
@@ -83,8 +84,9 @@ private static String determineSubpackageName(Path baseDir, Path resource) {
static URI determineClasspathResourceUri(Path baseDir, String basePackagePath, Path resource) {
String subPackageName = determineSubpackagePath(baseDir, resource);
String resourceName = resource.getFileName().toString();
- String classpathResourcePath = of(basePackagePath, subPackageName, resourceName)
- .filter(value -> !value.isEmpty()) // default package .
+ String classpathResourcePath = Stream.of(basePackagePath, subPackageName, resourceName)
+ // default package .
+ .filter(value -> !value.isEmpty())
.collect(joining(RESOURCE_SEPARATOR_STRING));
return classpathResourceUri(classpathResourcePath);
}
@@ -107,7 +109,7 @@ static URI classpathResourceUri(String classpathResourceName) {
static String determineFullyQualifiedClassName(Path baseDir, String basePackageName, Path classFile) {
String subpackageName = determineSubpackageName(baseDir, classFile);
String simpleClassName = determineSimpleClassName(classFile);
- return of(basePackageName, subpackageName, simpleClassName)
+ return Stream.of(basePackageName, subpackageName, simpleClassName)
.filter(value -> !value.isEmpty()) // default package
.collect(joining(PACKAGE_SEPARATOR_STRING));
}
@@ -154,34 +156,37 @@ public static URI rootPackageUri() {
}
public static String classPathScanningExplanation() {
- return "By default Cucumber scans the entire classpath for step definitions.\n" +
- "You can restrict this by configuring the glue path.\n" +
- "\n" +
- configurationExamples();
+ return """
+ By default Cucumber scans the entire classpath for step definitions.
+ You can restrict this by configuring the glue path.
+
+ %s""".formatted(configurationExamples());
}
static String nestedJarEntriesExplanation(URI uri) {
- return "By default Cucumber scans the entire classpath for step definitions.\n" +
- "However the resource '" + uri + "' is located in a nested jar.\n" +
- "\n" +
- "This typically happens when trying to run Cucumber inside a Spring Boot Executable Jar.\n" +
- "Cucumber currently doesn't support classpath scanning in nested jars.\n" +
- "\n" +
- "You can avoid this error by unpacking your application before executing or upgrading to Spring Boot 3.2 or higher.\n"
- +
- "\n" +
- "Alternatively you can restrict which packages cucumber scans configuring the glue path such that " +
- "Cucumber only scans un-nested jars.\n" +
- "\n" +
- configurationExamples();
+ return """
+ By default Cucumber scans the entire classpath for step definitions.
+ However the resource '%s' is located in a nested jar.
+
+ This typically happens when trying to run Cucumber inside a Spring Boot Executable Jar.
+ Cucumber currently doesn't support classpath scanning in nested jars.
+
+ You can avoid this error by unpacking your application before executing or upgrading to Spring Boot 3.2 or higher.
+
+ Alternatively you can restrict which packages cucumber scans configuring the glue path such that Cucumber only scans un-nested jars.
+
+ %s"""
+ .formatted(uri, configurationExamples());
}
public static String configurationExamples() {
- return "Examples:\n" +
- " - @CucumberOptions(glue = \"com.example.application\")\n" +
- " - @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = \"com.example.application\")\n" +
- " - src/test/resources/junit-platform.properties cucumber.glue=com.example.application\n" +
- " - src/test/resources/cucumber.properties cucumber.glue=com.example.application\n";
+ return """
+ Examples:
+ - @CucumberOptions(glue = "com.example.application")
+ - @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.example.application")
+ - src/test/resources/junit-platform.properties cucumber.glue=com.example.application
+ - src/test/resources/cucumber.properties cucumber.glue=com.example.application
+ """;
}
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/resource/CloseablePath.java b/cucumber-core/src/main/java/io/cucumber/core/resource/CloseablePath.java
index 73583f11e4..8eb99b4c1e 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/resource/CloseablePath.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/resource/CloseablePath.java
@@ -6,7 +6,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
-class CloseablePath implements Closeable {
+final class CloseablePath implements Closeable {
private static final Closeable NULL_CLOSEABLE = () -> {
};
diff --git a/cucumber-core/src/main/java/io/cucumber/core/resource/JarUriFileSystemService.java b/cucumber-core/src/main/java/io/cucumber/core/resource/JarUriFileSystemService.java
index 2af2320d14..05d8041191 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/resource/JarUriFileSystemService.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/resource/JarUriFileSystemService.java
@@ -15,8 +15,9 @@
import static io.cucumber.core.resource.ClasspathSupport.nestedJarEntriesExplanation;
import static java.util.Collections.emptyMap;
+import static java.util.Objects.requireNonNull;
-class JarUriFileSystemService {
+final class JarUriFileSystemService {
private static final String FILE_URI_SCHEME = "file";
private static final String JAR_URI_SCHEME = "jar";
@@ -27,6 +28,10 @@ class JarUriFileSystemService {
private static final Map openFiles = new HashMap<>();
private static final Map referenceCount = new HashMap<>();
+ private JarUriFileSystemService() {
+ /* no-op */
+ }
+
private static CloseablePath open(URI jarUri, Function pathProvider)
throws IOException {
FileSystem fileSystem = openFileSystem(jarUri);
@@ -35,7 +40,7 @@ private static CloseablePath open(URI jarUri, Function pathPro
}
private synchronized static void closeFileSystem(URI jarUri) throws IOException {
- int referents = referenceCount.get(jarUri).decrementAndGet();
+ int referents = requireNonNull(referenceCount.get(jarUri)).decrementAndGet();
if (referents == 0) {
openFiles.remove(jarUri).close();
referenceCount.remove(jarUri);
@@ -45,7 +50,7 @@ private synchronized static void closeFileSystem(URI jarUri) throws IOException
private synchronized static FileSystem openFileSystem(URI jarUri) throws IOException {
FileSystem existing = openFiles.get(jarUri);
if (existing != null) {
- referenceCount.get(jarUri).getAndIncrement();
+ requireNonNull(referenceCount.get(jarUri)).getAndIncrement();
return existing;
}
FileSystem fileSystem = FileSystems.newFileSystem(jarUri, emptyMap());
@@ -111,7 +116,7 @@ private static CloseablePath handleSpringBoot31JarUri(URI uri) throws IOExceptio
// Examples:
// jar:file:/home/user/application.jar!/BOOT-INF/lib/dependency.jar!/com/example/dependency/resource.txt
// jar:file:/home/user/application.jar!/BOOT-INF/classes!/com/example/package/resource.txt
- String[] parts = uri.toString().split("!");
+ String[] parts = uri.toString().split("!", 0);
String jarUri = parts[0];
String jarEntry = parts[1];
String subEntry = parts[2];
diff --git a/cucumber-core/src/main/java/io/cucumber/core/resource/PathScanner.java b/cucumber-core/src/main/java/io/cucumber/core/resource/PathScanner.java
index a4ec320864..8f8ad1725b 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/resource/PathScanner.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/resource/PathScanner.java
@@ -2,7 +2,6 @@
import io.cucumber.core.logging.Logger;
import io.cucumber.core.logging.LoggerFactory;
-import org.apiguardian.api.API;
import java.io.IOException;
import java.net.URI;
@@ -21,13 +20,15 @@
import static java.nio.file.FileVisitResult.CONTINUE;
import static java.nio.file.Files.exists;
import static java.nio.file.Files.walkFileTree;
-import static org.apiguardian.api.API.Status.INTERNAL;
-@API(status = INTERNAL)
-public class PathScanner {
+public final class PathScanner {
private static final Logger log = LoggerFactory.getLogger(PathScanner.class);
+ public PathScanner() {
+ /* no-op */
+ }
+
void findResourcesForUri(URI baseUri, Predicate filter, Function> consumer) {
try (CloseablePath closeablePath = open(baseUri)) {
Path baseDir = closeablePath.getPath();
diff --git a/cucumber-core/src/main/java/io/cucumber/core/resource/ResourceScanner.java b/cucumber-core/src/main/java/io/cucumber/core/resource/ResourceScanner.java
index ad0917baf0..00e044b08d 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/resource/ResourceScanner.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/resource/ResourceScanner.java
@@ -28,7 +28,6 @@
public final class ResourceScanner {
- private static final Predicate NULL_FILTER = x -> true;
private final PathScanner pathScanner = new PathScanner();
private final Supplier classLoaderSupplier;
private final Predicate canLoad;
@@ -121,17 +120,17 @@ public List scanForResourcesPath(Path resourcePath) {
pathScanner.findResourcesForPath(
resourcePath,
canLoad,
- processResource(DEFAULT_PACKAGE_NAME, NULL_FILTER, createUriResource(), resources::add));
+ processResource(DEFAULT_PACKAGE_NAME, x -> true, createUriResource(), resources::add));
return resources;
}
public List scanForResourcesUri(URI classpathResourceUri) {
requireNonNull(classpathResourceUri, "classpathResourceUri must not be null");
if (CLASSPATH_SCHEME.equals(classpathResourceUri.getScheme())) {
- return scanForClasspathResource(resourceName(classpathResourceUri), NULL_FILTER);
+ return scanForClasspathResource(resourceName(classpathResourceUri), x -> true);
}
- return findResourcesForUri(classpathResourceUri, DEFAULT_PACKAGE_NAME, NULL_FILTER, createUriResource());
+ return findResourcesForUri(classpathResourceUri, DEFAULT_PACKAGE_NAME, x -> true, createUriResource());
}
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/resource/Resources.java b/cucumber-core/src/main/java/io/cucumber/core/resource/Resources.java
index ea08d5f424..bad3980892 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/resource/Resources.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/resource/Resources.java
@@ -11,10 +11,10 @@
import static io.cucumber.core.resource.ClasspathSupport.determineClasspathResourceUri;
import static io.cucumber.core.resource.ClasspathSupport.resourceNameOfPackageName;
-class Resources {
+final class Resources {
private Resources() {
-
+ /* no-op */
}
static BiFunction createPackageResource(String packageName) {
diff --git a/cucumber-core/src/main/java/io/cucumber/core/resource/package-info.java b/cucumber-core/src/main/java/io/cucumber/core/resource/package-info.java
new file mode 100644
index 0000000000..2b222a1c4b
--- /dev/null
+++ b/cucumber-core/src/main/java/io/cucumber/core/resource/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package io.cucumber.core.resource;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/cucumber-core/src/main/java/io/cucumber/core/runner/CachingGlue.java b/cucumber-core/src/main/java/io/cucumber/core/runner/CachingGlue.java
index fbdbd66d52..3b1cb4253e 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/runner/CachingGlue.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/runner/CachingGlue.java
@@ -36,6 +36,7 @@
import io.cucumber.messages.types.StepDefinitionPattern;
import io.cucumber.messages.types.StepDefinitionPatternType;
import io.cucumber.plugin.event.StepDefinedEvent;
+import org.jspecify.annotations.Nullable;
import java.net.URI;
import java.util.ArrayList;
@@ -49,6 +50,8 @@
import java.util.Map;
import java.util.TreeMap;
+import static java.util.Objects.requireNonNull;
+
final class CachingGlue implements Glue {
private static final Comparator HOOK_ORDER_ASCENDING = Comparator
@@ -84,9 +87,9 @@ final class CachingGlue implements Glue {
private final EventBus bus;
- private StepTypeRegistry stepTypeRegistry;
- private Locale locale = null;
- private StepExpressionFactory stepExpressionFactory = null;
+ private @Nullable StepTypeRegistry stepTypeRegistry;
+ private @Nullable Locale locale = null;
+ private @Nullable StepExpressionFactory stepExpressionFactory = null;
private boolean cacheIsDirty = false;
private boolean hasScenarioScopedGlue = false;
@@ -245,7 +248,7 @@ List getDocStringTypeDefinitions() {
}
StepTypeRegistry getStepTypeRegistry() {
- return stepTypeRegistry;
+ return requireNonNull(stepTypeRegistry);
}
void prepareGlue(Locale locale) throws DuplicateStepDefinitionException {
@@ -343,19 +346,11 @@ private void emitHook(CoreHookDefinition coreHook) {
.orElseGet(this::emptySourceReference),
coreHook.getTagExpression(),
coreHook.getHookType()
- .map(hookType -> {
- switch (hookType) {
- case BEFORE:
- return HookType.BEFORE_TEST_CASE;
- case AFTER:
- return HookType.AFTER_TEST_CASE;
- case BEFORE_STEP:
- return HookType.BEFORE_TEST_STEP;
- case AFTER_STEP:
- return HookType.AFTER_TEST_STEP;
- default:
- return null;
- }
+ .map(hookType -> switch (hookType) {
+ case BEFORE -> HookType.BEFORE_TEST_CASE;
+ case AFTER -> HookType.AFTER_TEST_CASE;
+ case BEFORE_STEP -> HookType.BEFORE_TEST_STEP;
+ case AFTER_STEP -> HookType.AFTER_TEST_STEP;
})
.orElse(null));
bus.send(Envelope.of(messagesHook));
@@ -380,23 +375,21 @@ private void emitStepDefined(CoreStepDefinition coreStepDefinition) {
}
private SourceReference createSourceReference(io.cucumber.core.backend.SourceReference reference) {
- if (reference instanceof JavaMethodReference) {
- JavaMethodReference methodReference = (JavaMethodReference) reference;
+ if (reference instanceof JavaMethodReference methodReference) {
return SourceReference.of(new JavaMethod(
methodReference.className(),
methodReference.methodName(),
methodReference.methodParameterTypes()));
}
- if (reference instanceof StackTraceElementReference) {
- StackTraceElementReference stackReference = (StackTraceElementReference) reference;
+ if (reference instanceof StackTraceElementReference stackReference) {
JavaStackTraceElement stackTraceElement = new JavaStackTraceElement(
stackReference.className(),
// TODO: Fix json schema. Stacktrace elements need not have a
// source file
stackReference.fileName().orElse("Unknown"),
stackReference.methodName());
- Location location = new Location((long) stackReference.lineNumber(), null);
+ Location location = new Location(stackReference.lineNumber(), null);
return new SourceReference(null, null, stackTraceElement, location);
}
return emptySourceReference();
@@ -417,6 +410,7 @@ private StepDefinitionPatternType getExpressionType(CoreStepDefinition stepDefin
}
}
+ @Nullable
PickleStepDefinitionMatch stepDefinitionMatch(URI uri, Step step) throws AmbiguousStepDefinitionsException {
PickleStepDefinitionMatch cachedMatch = cachedStepDefinitionMatch(uri, step);
if (cachedMatch != null) {
@@ -425,7 +419,7 @@ PickleStepDefinitionMatch stepDefinitionMatch(URI uri, Step step) throws Ambiguo
return findStepDefinitionMatch(uri, step);
}
- private PickleStepDefinitionMatch cachedStepDefinitionMatch(URI uri, Step step) {
+ private @Nullable PickleStepDefinitionMatch cachedStepDefinitionMatch(URI uri, Step step) {
String stepDefinitionPattern = stepPatternByStepText.get(step.getText());
if (stepDefinitionPattern == null) {
return null;
@@ -436,18 +430,16 @@ private PickleStepDefinitionMatch cachedStepDefinitionMatch(URI uri, Step step)
return null;
}
- // Step definition arguments consists of parameters included in the step
- // text and
- // gherkin step arguments (doc string and data table) which are not
- // included in
- // the step text. As such the step definition arguments can not be
- // cached and
- // must be recreated each time.
- List arguments = coreStepDefinition.matchedArguments(step);
+ // At this point we know the step matches but:
+ // Step definition arguments consists of parameters included in the
+ // step text and gherkin step arguments (doc string and data table)
+ // which are not included in the step text. As such the step definition
+ // arguments can not be cached and must be recreated each time.
+ List arguments = requireNonNull(coreStepDefinition.matchedArguments(step));
return new PickleStepDefinitionMatch(arguments, coreStepDefinition, uri, step);
}
- private PickleStepDefinitionMatch findStepDefinitionMatch(URI uri, Step step)
+ private @Nullable PickleStepDefinitionMatch findStepDefinitionMatch(URI uri, Step step)
throws AmbiguousStepDefinitionsException {
List matches = stepDefinitionMatches(uri, step);
if (matches.isEmpty()) {
@@ -498,8 +490,7 @@ private void removeScenarioScopedGlue(Iterable> glues) {
Iterator> glueIterator = glues.iterator();
while (glueIterator.hasNext()) {
Object glue = glueIterator.next();
- if (glue instanceof ScenarioScoped) {
- ScenarioScoped scenarioScoped = (ScenarioScoped) glue;
+ if (glue instanceof ScenarioScoped scenarioScoped) {
scenarioScoped.dispose();
glueIterator.remove();
cacheIsDirty = true;
diff --git a/cucumber-core/src/main/java/io/cucumber/core/runner/CoreDefaultDataTableEntryTransformerDefinition.java b/cucumber-core/src/main/java/io/cucumber/core/runner/CoreDefaultDataTableEntryTransformerDefinition.java
index 5272271795..9017b3a354 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/runner/CoreDefaultDataTableEntryTransformerDefinition.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/runner/CoreDefaultDataTableEntryTransformerDefinition.java
@@ -60,8 +60,7 @@ private static class ScenarioCoreDefaultDataTableEntryTransformerDefinition
@Override
public void dispose() {
- if (delegate instanceof ScenarioScoped) {
- ScenarioScoped scenarioScoped = (ScenarioScoped) delegate;
+ if (delegate instanceof ScenarioScoped scenarioScoped) {
scenarioScoped.dispose();
}
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/runner/CoreHookDefinition.java b/cucumber-core/src/main/java/io/cucumber/core/runner/CoreHookDefinition.java
index 88b3d4c7bd..1a7cc18552 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/runner/CoreHookDefinition.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/runner/CoreHookDefinition.java
@@ -75,7 +75,11 @@ Optional getHookType() {
return delegate.getHookType();
}
- static class ScenarioScopedCoreHookDefinition extends CoreHookDefinition implements ScenarioScoped {
+ Optional getDefinitionLocation() {
+ return delegate.getSourceReference();
+ }
+
+ static final class ScenarioScopedCoreHookDefinition extends CoreHookDefinition implements ScenarioScoped {
private ScenarioScopedCoreHookDefinition(UUID id, HookDefinition delegate) {
super(id, delegate);
@@ -83,15 +87,11 @@ private ScenarioScopedCoreHookDefinition(UUID id, HookDefinition delegate) {
@Override
public void dispose() {
- if (delegate instanceof ScenarioScoped) {
- ScenarioScoped scenarioScoped = (ScenarioScoped) delegate;
+ if (delegate instanceof ScenarioScoped scenarioScoped) {
scenarioScoped.dispose();
}
}
}
- Optional getDefinitionLocation() {
- return delegate.getSourceReference();
- }
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java b/cucumber-core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java
index f0f55c47cd..f30d0a279e 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/runner/CoreStepDefinition.java
@@ -9,6 +9,7 @@
import io.cucumber.core.stepexpression.Argument;
import io.cucumber.core.stepexpression.ArgumentMatcher;
import io.cucumber.core.stepexpression.StepExpression;
+import org.jspecify.annotations.Nullable;
import java.lang.reflect.Type;
import java.util.List;
@@ -53,6 +54,7 @@ StepDefinition getStepDefinition() {
return stepDefinition;
}
+ @Nullable
List matchedArguments(Step step) {
return argumentMatcher.argumentsFrom(step, types);
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/runner/DefinitionArgument.java b/cucumber-core/src/main/java/io/cucumber/core/runner/DefinitionArgument.java
index e9d314add8..7181328d8b 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/runner/DefinitionArgument.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/runner/DefinitionArgument.java
@@ -2,6 +2,7 @@
import io.cucumber.core.stepexpression.ExpressionArgument;
import io.cucumber.plugin.event.Argument;
+import org.jspecify.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
@@ -21,8 +22,7 @@ private DefinitionArgument(ExpressionArgument argument) {
static List createArguments(List match) {
List args = new ArrayList<>();
for (io.cucumber.core.stepexpression.Argument argument : match) {
- if (argument instanceof ExpressionArgument) {
- ExpressionArgument expressionArgument = (ExpressionArgument) argument;
+ if (argument instanceof ExpressionArgument expressionArgument) {
args.add(new DefinitionArgument(expressionArgument));
}
}
@@ -35,23 +35,23 @@ public String getParameterTypeName() {
}
@Override
- public String getValue() {
- return group == null ? null : group.getValue();
+ public @Nullable String getValue() {
+ return group.getValue();
}
@Override
public int getStart() {
- return group == null ? -1 : group.getStart();
+ return group.getStart();
}
@Override
public int getEnd() {
- return group == null ? -1 : group.getEnd();
+ return group.getEnd();
}
@Override
public io.cucumber.plugin.event.Group getGroup() {
- return group == null ? null : new Group(group);
+ return new Group(group);
}
private static final class Group implements io.cucumber.plugin.event.Group {
@@ -72,7 +72,7 @@ public Collection getChildren() {
}
@Override
- public String getValue() {
+ public @Nullable String getValue() {
return group.getValue();
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/runner/NoStepDefinition.java b/cucumber-core/src/main/java/io/cucumber/core/runner/NoStepDefinition.java
index 1a5f5e403a..32183ebfc5 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/runner/NoStepDefinition.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/runner/NoStepDefinition.java
@@ -3,6 +3,7 @@
import io.cucumber.core.backend.ParameterInfo;
import io.cucumber.core.backend.StepDefinition;
+import java.util.Collections;
import java.util.List;
final class NoStepDefinition implements StepDefinition {
@@ -13,12 +14,12 @@ public void execute(Object[] args) {
@Override
public List parameterInfos() {
- return null;
+ return Collections.emptyList();
}
@Override
public String getPattern() {
- return null;
+ return "";
}
@Override
@@ -28,7 +29,7 @@ public boolean isDefinedAt(StackTraceElement stackTraceElement) {
@Override
public String getLocation() {
- return null;
+ return "no location";
}
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/runner/Options.java b/cucumber-core/src/main/java/io/cucumber/core/runner/Options.java
index 0fe0ffb807..4fb2406dfa 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/runner/Options.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/runner/Options.java
@@ -3,6 +3,7 @@
import io.cucumber.core.backend.ObjectFactory;
import io.cucumber.core.eventbus.UuidGenerator;
import io.cucumber.core.snippets.SnippetType;
+import org.jspecify.annotations.Nullable;
import java.net.URI;
import java.util.List;
@@ -15,8 +16,10 @@ public interface Options {
SnippetType getSnippetType();
+ @Nullable
Class extends ObjectFactory> getObjectFactoryClass();
+ @Nullable
Class extends UuidGenerator> getUuidGeneratorClass();
}
diff --git a/cucumber-core/src/main/java/io/cucumber/core/runner/PickleStepDefinitionMatch.java b/cucumber-core/src/main/java/io/cucumber/core/runner/PickleStepDefinitionMatch.java
index 84ad6b0580..44d84ec7f3 100644
--- a/cucumber-core/src/main/java/io/cucumber/core/runner/PickleStepDefinitionMatch.java
+++ b/cucumber-core/src/main/java/io/cucumber/core/runner/PickleStepDefinitionMatch.java
@@ -12,6 +12,7 @@
import io.cucumber.datatable.CucumberDataTableException;
import io.cucumber.datatable.UndefinedDataTableTypeException;
import io.cucumber.docstring.CucumberDocStringException;
+import org.jspecify.annotations.Nullable;
import java.net.URI;
import java.util.ArrayList;
@@ -37,10 +38,10 @@ class PickleStepDefinitionMatch extends Match implements StepDefinitionMatch {
public void runStep(TestCaseState state) throws Throwable {
List arguments = getArguments();
List parameterInfos = stepDefinition.parameterInfos();
- if (parameterInfos != null && arguments.size() != parameterInfos.size()) {
+ if (arguments.size() != parameterInfos.size()) {
throw arityMismatch(parameterInfos.size());
}
- List