glueClass);
-
}
diff --git a/core/src/main/java/io/cucumber/core/backend/ObjectFactoryServiceLoader.java b/core/src/main/java/io/cucumber/core/backend/ObjectFactoryServiceLoader.java
new file mode 100644
index 0000000000..aaa8259d75
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/backend/ObjectFactoryServiceLoader.java
@@ -0,0 +1,117 @@
+package io.cucumber.core.backend;
+
+import io.cucumber.core.exception.CucumberException;
+
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+import static java.util.Objects.requireNonNull;
+
+public final class ObjectFactoryServiceLoader {
+
+ private final Options options;
+
+ public ObjectFactoryServiceLoader(Options options) {
+ this.options = requireNonNull(options);
+ }
+
+ /**
+ * Loads an instance of {@link ObjectFactory} using the {@link ServiceLoader} mechanism.
+ *
+ * Will load an instance of the class provided by {@link Options#getObjectFactoryClass()}.
+ *
+ * If {@link Options#getObjectFactoryClass()} does not provide a class and there is exactly
+ * one {@code ObjectFactory} instance available that instance will be used.
+ *
+ * Otherwise a default object factory with no dependency injection capabilities will be used.
+ *
+ * @return an instance of {@link ObjectFactory}
+ */
+ public ObjectFactory loadObjectFactory() {
+ Class extends ObjectFactory> objectFactoryClass = this.options.getObjectFactoryClass();
+
+ final ServiceLoader loader = ServiceLoader.load(ObjectFactory.class);
+ if (objectFactoryClass == null) {
+ return loadSingleObjectFactoryOrDefault(loader);
+
+ }
+
+ return loadSelectedObjectFactory(loader, objectFactoryClass);
+ }
+
+ private static ObjectFactory loadSelectedObjectFactory(ServiceLoader loader, Class extends ObjectFactory> objectFactoryClass) {
+ for (ObjectFactory objectFactory : loader) {
+ if (objectFactoryClass.equals(objectFactory.getClass())) {
+ return objectFactory;
+ }
+ }
+
+ throw new CucumberException("Could not find object factory " + objectFactoryClass);
+ }
+
+ private static ObjectFactory loadSingleObjectFactoryOrDefault(ServiceLoader loader) {
+ final Iterator objectFactories = loader.iterator();
+
+ ObjectFactory objectFactory;
+ if (objectFactories.hasNext()) {
+ objectFactory = objectFactories.next();
+ } else {
+ objectFactory = new DefaultJavaObjectFactory();
+ }
+
+ if (objectFactories.hasNext()) {
+ System.out.println(getMultipleObjectFactoryLogMessage());
+ objectFactory = new DefaultJavaObjectFactory();
+ }
+ return objectFactory;
+ }
+
+ private static String getMultipleObjectFactoryLogMessage() {
+ return "More than one Cucumber ObjectFactory was found in the classpath\n" +
+ "\n" +
+ "You probably may have included, for instance, cucumber-spring AND cucumber-guice as part of\n" +
+ "your dependencies. When this happens, Cucumber falls back to instantiating the\n" +
+ "DefaultJavaObjectFactory implementation which doesn't provide IoC.\n" +
+ "In order to enjoy IoC features, please remove the unnecessary dependencies from your classpath.\n";
+ }
+
+ static class DefaultJavaObjectFactory implements ObjectFactory {
+ private final Map, Object> instances = new HashMap<>();
+
+ public void start() {
+ // No-op
+ }
+
+ public void stop() {
+ instances.clear();
+ }
+
+ public boolean addClass(Class> clazz) {
+ return true;
+ }
+
+ public T getInstance(Class type) {
+ T instance = type.cast(instances.get(type));
+ if (instance == null) {
+ instance = cacheNewInstance(type);
+ }
+ return instance;
+ }
+
+ private T cacheNewInstance(Class type) {
+ try {
+ Constructor constructor = type.getConstructor();
+ T instance = constructor.newInstance();
+ instances.put(type, instance);
+ return instance;
+ } catch (NoSuchMethodException e) {
+ throw new CucumberException(String.format("%s doesn't have an empty constructor. If you need DI, put cucumber-picocontainer on the classpath", type), e);
+ } catch (Exception e) {
+ throw new CucumberException(String.format("Failed to instantiate %s", type), e);
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/io/cucumber/core/backend/Options.java b/core/src/main/java/io/cucumber/core/backend/Options.java
new file mode 100644
index 0000000000..159d60a833
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/backend/Options.java
@@ -0,0 +1,6 @@
+package io.cucumber.core.backend;
+
+public interface Options {
+
+ Class extends ObjectFactory> getObjectFactoryClass();
+}
diff --git a/core/src/main/java/io/cucumber/core/backend/ParameterInfo.java b/core/src/main/java/io/cucumber/core/backend/ParameterInfo.java
new file mode 100644
index 0000000000..99c734edf6
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/backend/ParameterInfo.java
@@ -0,0 +1,12 @@
+package io.cucumber.core.backend;
+
+import java.lang.reflect.Type;
+
+public interface ParameterInfo {
+
+ Type getType();
+
+ boolean isTransposed();
+
+ TypeResolver getTypeResolver();
+}
diff --git a/core/src/main/java/io/cucumber/core/backend/ParameterTypeDefinition.java b/core/src/main/java/io/cucumber/core/backend/ParameterTypeDefinition.java
new file mode 100644
index 0000000000..e0956cbcb4
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/backend/ParameterTypeDefinition.java
@@ -0,0 +1,19 @@
+package io.cucumber.core.backend;
+
+import io.cucumber.cucumberexpressions.ParameterType;
+import org.apiguardian.api.API;
+
+@API(status = API.Status.EXPERIMENTAL)
+public interface ParameterTypeDefinition {
+
+ ParameterType> parameterType();
+
+ /**
+ * The source line where the parameter type is defined.
+ * Example: com/example/app/Cucumber.test():42
+ *
+ * @param detail true if extra detailed location information should be included.
+ * @return The source line of the step definition.
+ */
+ String getLocation(boolean detail);
+}
diff --git a/core/src/main/java/cucumber/api/Pending.java b/core/src/main/java/io/cucumber/core/backend/Pending.java
similarity index 82%
rename from core/src/main/java/cucumber/api/Pending.java
rename to core/src/main/java/io/cucumber/core/backend/Pending.java
index c8460c5298..4d864857b4 100644
--- a/core/src/main/java/cucumber/api/Pending.java
+++ b/core/src/main/java/io/cucumber/core/backend/Pending.java
@@ -1,4 +1,7 @@
-package cucumber.api;
+package io.cucumber.core.backend;
+
+
+import org.apiguardian.api.API;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -9,9 +12,8 @@
* Any exception class annotated with this annotation will be treated as a "pending" exception.
* That is - if the exception is thrown from a step definition or hook, the scenario's status will
* be pending instead of failed.
- *
- * @see PendingException
*/
+@API(status = API.Status.STABLE)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Pending {
diff --git a/core/src/main/java/io/cucumber/core/backend/StepDefinition.java b/core/src/main/java/io/cucumber/core/backend/StepDefinition.java
new file mode 100644
index 0000000000..bbeac8cba0
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/backend/StepDefinition.java
@@ -0,0 +1,30 @@
+package io.cucumber.core.backend;
+
+import org.apiguardian.api.API;
+
+import java.util.List;
+
+@API(status = API.Status.STABLE)
+public interface StepDefinition extends io.cucumber.core.event.StepDefinition {
+ /**
+ * Invokes the step definition. The method should raise a Throwable
+ * if the invocation fails, which will cause the step to fail.
+ *
+ * @param args The arguments for the step
+ * @throws Throwable in case of step failure.
+ */
+ void execute(Object[] args) throws Throwable;
+
+ /**
+ * @param stackTraceElement The location of the step.
+ * @return Return true if this matches the location. This is used to filter
+ * stack traces.
+ */
+ boolean isDefinedAt(StackTraceElement stackTraceElement);
+
+ /**
+ * @return parameter information or null when the language does not provide parameter information
+ */
+ List parameterInfos();
+
+}
diff --git a/core/src/main/java/io/cucumber/core/backend/TypeResolver.java b/core/src/main/java/io/cucumber/core/backend/TypeResolver.java
new file mode 100644
index 0000000000..5e3199a6eb
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/backend/TypeResolver.java
@@ -0,0 +1,22 @@
+package io.cucumber.core.backend;
+
+import org.apiguardian.api.API;
+
+import java.lang.reflect.Type;
+
+/**
+ * Allows lazy resolution of the type of a data table or doc string.
+ */
+@API(status = API.Status.STABLE)
+public interface TypeResolver {
+
+ /**
+ * A type to data convert the table or doc string to. May not return null.
+ *
+ * When the {@link Object} type is returned no transform will be applied.
+ *
+ * @return a type
+ */
+ Type resolve();
+
+}
diff --git a/core/src/main/java/io/cucumber/core/cli/Main.java b/core/src/main/java/io/cucumber/core/cli/Main.java
index 87ffb002f1..c03b3a9985 100644
--- a/core/src/main/java/io/cucumber/core/cli/Main.java
+++ b/core/src/main/java/io/cucumber/core/cli/Main.java
@@ -1,13 +1,27 @@
package io.cucumber.core.cli;
-import cucumber.runtime.Env;
-import cucumber.runtime.Runtime;
-import cucumber.runtime.io.MultiLoader;
-import cucumber.runtime.io.ResourceLoader;
+import io.cucumber.core.io.MultiLoader;
+import io.cucumber.core.io.ResourceLoader;
import io.cucumber.core.options.CommandlineOptionsParser;
-import io.cucumber.core.options.EnvironmentOptionsParser;
+import io.cucumber.core.options.Constants;
+import io.cucumber.core.options.CucumberProperties;
+import io.cucumber.core.options.CucumberPropertiesParser;
import io.cucumber.core.options.RuntimeOptions;
+import io.cucumber.core.runtime.Runtime;
+import org.apiguardian.api.API;
+/**
+ * Cucumber Main. Runs Cucumber as a CLI.
+ *
+ * Options can be provided in order of precedence through:
+ *
+ * - command line arguments
+ * - {@value Constants#CUCUMBER_OPTIONS_PROPERTY_NAME} property in {@link System#getProperties()}
+ * - {@value Constants#CUCUMBER_OPTIONS_PROPERTY_NAME} property in {@link System#getenv()}
+ * - {@value Constants#CUCUMBER_OPTIONS_PROPERTY_NAME} property in {@value Constants#CUCUMBER_PROPERTIES_FILE_NAME}
+ *
+ */
+@API(status = API.Status.STABLE)
public class Main {
public static void main(String[] argv) {
@@ -25,15 +39,22 @@ public static void main(String[] argv) {
public static byte run(String[] argv, ClassLoader classLoader) {
ResourceLoader resourceLoader = new MultiLoader(classLoader);
- RuntimeOptions runtimeOptions = new CommandlineOptionsParser(resourceLoader)
- .parse(argv)
- .addDefaultFormatterIfNotPresent()
- .addDefaultSummaryPrinterIfNotPresent()
+ RuntimeOptions propertiesFileOptions = new CucumberPropertiesParser(resourceLoader)
+ .parse(CucumberProperties.fromPropertiesFile())
.build();
- new EnvironmentOptionsParser(resourceLoader)
- .parse(Env.INSTANCE)
- .build(runtimeOptions);
+ RuntimeOptions environmentOptions = new CucumberPropertiesParser(resourceLoader)
+ .parse(CucumberProperties.fromEnvironment())
+ .build(propertiesFileOptions);
+
+ RuntimeOptions systemOptions = new CucumberPropertiesParser(resourceLoader)
+ .parse(CucumberProperties.fromSystemProperties())
+ .build(environmentOptions);
+
+ RuntimeOptions runtimeOptions = new CommandlineOptionsParser()
+ .parse(argv)
+ .build(systemOptions);
+
final Runtime runtime = Runtime.builder()
.withRuntimeOptions(runtimeOptions)
diff --git a/core/src/main/java/cucumber/api/Argument.java b/core/src/main/java/io/cucumber/core/event/Argument.java
similarity index 79%
rename from core/src/main/java/cucumber/api/Argument.java
rename to core/src/main/java/io/cucumber/core/event/Argument.java
index 13906ace1f..6a71b74b79 100644
--- a/core/src/main/java/cucumber/api/Argument.java
+++ b/core/src/main/java/io/cucumber/core/event/Argument.java
@@ -1,4 +1,6 @@
-package cucumber.api;
+package io.cucumber.core.event;
+
+import org.apiguardian.api.API;
/**
* Represents an argument in for a step definition.
@@ -8,6 +10,7 @@
* one argument with value {@code "4"}, starting at {@code 7} and
* ending at {@code 8}.
*/
+@API(status = API.Status.STABLE)
public interface Argument {
String getValue();
diff --git a/core/src/main/java/io/cucumber/core/event/EmbedEvent.java b/core/src/main/java/io/cucumber/core/event/EmbedEvent.java
new file mode 100644
index 0000000000..0dfd4e1d3d
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/event/EmbedEvent.java
@@ -0,0 +1,39 @@
+package io.cucumber.core.event;
+
+import org.apiguardian.api.API;
+
+import java.time.Instant;
+import java.util.Objects;
+
+@API(status = API.Status.STABLE)
+public final class EmbedEvent extends TestCaseEvent {
+ private final byte[] data;
+ private final String mimeType;
+ public final String name;
+
+ public EmbedEvent(Instant timeInstant, TestCase testCase, byte[] data, String mimeType) {
+ super(timeInstant, testCase);
+ this.data = Objects.requireNonNull(data);
+ this.mimeType = Objects.requireNonNull(mimeType);
+ this.name = null;
+ }
+
+ public EmbedEvent(Instant timeInstant, TestCase testCase, byte[] data, String mimeType, String name) {
+ super(timeInstant, testCase);
+ this.data = data;
+ this.mimeType = mimeType;
+ this.name = name;
+ }
+
+ public byte[] getData() {
+ return data;
+ }
+
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/core/src/main/java/io/cucumber/core/event/Event.java b/core/src/main/java/io/cucumber/core/event/Event.java
new file mode 100644
index 0000000000..ed44e4b614
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/event/Event.java
@@ -0,0 +1,17 @@
+package io.cucumber.core.event;
+
+import org.apiguardian.api.API;
+
+import java.time.Instant;
+
+@API(status = API.Status.STABLE)
+public interface Event {
+
+ /**
+ * Returns instant from epoch.
+ *
+ * @return time instant in Instant
+ * @see Instant#now()
+ */
+ Instant getInstant();
+}
diff --git a/core/src/main/java/io/cucumber/core/event/EventHandler.java b/core/src/main/java/io/cucumber/core/event/EventHandler.java
new file mode 100644
index 0000000000..d9a6d15d66
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/event/EventHandler.java
@@ -0,0 +1,10 @@
+package io.cucumber.core.event;
+
+import org.apiguardian.api.API;
+
+@API(status = API.Status.STABLE)
+public interface EventHandler {
+
+ void receive(T event);
+
+}
diff --git a/core/src/main/java/cucumber/api/event/EventPublisher.java b/core/src/main/java/io/cucumber/core/event/EventPublisher.java
similarity index 95%
rename from core/src/main/java/cucumber/api/event/EventPublisher.java
rename to core/src/main/java/io/cucumber/core/event/EventPublisher.java
index 742babdd75..00aca48ecd 100644
--- a/core/src/main/java/cucumber/api/event/EventPublisher.java
+++ b/core/src/main/java/io/cucumber/core/event/EventPublisher.java
@@ -1,5 +1,8 @@
-package cucumber.api.event;
+package io.cucumber.core.event;
+import org.apiguardian.api.API;
+
+@API(status = API.Status.STABLE)
public interface EventPublisher {
/**
diff --git a/core/src/main/java/cucumber/api/HookTestStep.java b/core/src/main/java/io/cucumber/core/event/HookTestStep.java
similarity index 66%
rename from core/src/main/java/cucumber/api/HookTestStep.java
rename to core/src/main/java/io/cucumber/core/event/HookTestStep.java
index c014c498ac..64416652b3 100644
--- a/core/src/main/java/cucumber/api/HookTestStep.java
+++ b/core/src/main/java/io/cucumber/core/event/HookTestStep.java
@@ -1,12 +1,15 @@
-package cucumber.api;
+package io.cucumber.core.event;
+
+import org.apiguardian.api.API;
/**
* Hooks are invoked before and after each scenario and before and
* after each gherkin step in a scenario.
*
- * @see cucumber.api.event.TestCaseStarted
- * @see cucumber.api.event.TestCaseFinished
+ * @see TestCaseStarted
+ * @see TestCaseFinished
*/
+@API(status = API.Status.STABLE)
public interface HookTestStep extends TestStep {
/**
diff --git a/core/src/main/java/io/cucumber/core/event/HookType.java b/core/src/main/java/io/cucumber/core/event/HookType.java
new file mode 100644
index 0000000000..70d8f28865
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/event/HookType.java
@@ -0,0 +1,8 @@
+package io.cucumber.core.event;
+
+import org.apiguardian.api.API;
+
+@API(status = API.Status.STABLE)
+public enum HookType {
+ BEFORE, AFTER, BEFORE_STEP, AFTER_STEP;
+}
diff --git a/core/src/main/java/cucumber/api/PickleStepTestStep.java b/core/src/main/java/io/cucumber/core/event/PickleStepTestStep.java
similarity index 93%
rename from core/src/main/java/cucumber/api/PickleStepTestStep.java
rename to core/src/main/java/io/cucumber/core/event/PickleStepTestStep.java
index 7e63f8b797..485e459586 100644
--- a/core/src/main/java/cucumber/api/PickleStepTestStep.java
+++ b/core/src/main/java/io/cucumber/core/event/PickleStepTestStep.java
@@ -1,10 +1,13 @@
-package cucumber.api;
+package io.cucumber.core.event;
+
+import org.apiguardian.api.API;
import java.util.List;
/**
* A pickle test step matches a line in a Gherkin scenario or background.
*/
+@API(status = API.Status.STABLE)
public interface PickleStepTestStep extends TestStep {
/**
diff --git a/core/src/main/java/io/cucumber/core/event/Result.java b/core/src/main/java/io/cucumber/core/event/Result.java
new file mode 100644
index 0000000000..408bbe768a
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/event/Result.java
@@ -0,0 +1,65 @@
+package io.cucumber.core.event;
+
+import org.apiguardian.api.API;
+
+import java.time.Duration;
+import java.util.Objects;
+
+import static java.util.Objects.requireNonNull;
+
+@API(status = API.Status.STABLE)
+public final class Result{
+
+ private final Status status;
+ private final Duration duration;
+ private final Throwable error;
+
+ /**
+ * The result of a step or scenario
+ *
+ * @param status status of the step or scenario
+ * @param duration the duration
+ * @param error the error that caused the failure if any
+ */
+ public Result(Status status, Duration duration, Throwable error) {
+ this.status = requireNonNull(status);
+ this.duration = requireNonNull(duration);
+ this.error = error;
+ }
+
+ public Status getStatus() {
+ return status;
+ }
+
+ public Duration getDuration() {
+ return duration;
+ }
+
+ public Throwable getError() {
+ return error;
+ }
+
+ @Override
+ public String toString() {
+ return "Result{" +
+ "status=" + status +
+ ", duration=" + duration.getSeconds() +
+ ", error=" + error +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Result result = (Result) o;
+ return status == result.status &&
+ Objects.equals(duration, result.duration) &&
+ Objects.equals(error, result.error);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(status, duration, error);
+ }
+}
diff --git a/core/src/main/java/io/cucumber/core/event/SnippetsSuggestedEvent.java b/core/src/main/java/io/cucumber/core/event/SnippetsSuggestedEvent.java
new file mode 100644
index 0000000000..c881e0b972
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/event/SnippetsSuggestedEvent.java
@@ -0,0 +1,53 @@
+package io.cucumber.core.event;
+
+import org.apiguardian.api.API;
+
+import java.time.Instant;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+@API(status = API.Status.STABLE)
+public final class SnippetsSuggestedEvent extends TimeStampedEvent {
+ private final String uri;
+ private final List stepLocations;
+ private final List snippets;
+
+ public SnippetsSuggestedEvent(Instant timeInstant, String uri, List stepLocations, List snippets) {
+ super(timeInstant);
+ this.uri = Objects.requireNonNull(uri);
+ this.stepLocations = Objects.requireNonNull(stepLocations);
+ this.snippets = Collections.unmodifiableList(Objects.requireNonNull(snippets));
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ public List getStepLocations() {
+ return stepLocations;
+ }
+
+ public List getSnippets() {
+ return snippets;
+ }
+
+ public static final class Location {
+ private final int line;
+ private final int column;
+
+ public Location(int line, int column) {
+ this.line = line;
+ this.column = column;
+ }
+
+ public int getLine() {
+ return line;
+ }
+
+ public int getColumn() {
+ return column;
+ }
+ }
+
+}
diff --git a/core/src/main/java/io/cucumber/core/event/Status.java b/core/src/main/java/io/cucumber/core/event/Status.java
index 94d6c2432f..ea9cd72a60 100644
--- a/core/src/main/java/io/cucumber/core/event/Status.java
+++ b/core/src/main/java/io/cucumber/core/event/Status.java
@@ -1,5 +1,8 @@
package io.cucumber.core.event;
+import org.apiguardian.api.API;
+
+@API(status = API.Status.STABLE)
public enum Status {
PASSED,
SKIPPED,
diff --git a/core/src/main/java/io/cucumber/core/event/StepDefinedEvent.java b/core/src/main/java/io/cucumber/core/event/StepDefinedEvent.java
new file mode 100644
index 0000000000..54c5f4dc1d
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/event/StepDefinedEvent.java
@@ -0,0 +1,21 @@
+package io.cucumber.core.event;
+
+
+import org.apiguardian.api.API;
+
+import java.time.Instant;
+import java.util.Objects;
+
+@API(status = API.Status.STABLE)
+public final class StepDefinedEvent extends TimeStampedEvent {
+ private final StepDefinition stepDefinition;
+
+ public StepDefinedEvent(Instant timeInstant, StepDefinition stepDefinition) {
+ super(timeInstant);
+ this.stepDefinition = Objects.requireNonNull(stepDefinition);
+ }
+
+ public StepDefinition getStepDefinition() {
+ return stepDefinition;
+ }
+}
diff --git a/core/src/main/java/io/cucumber/core/event/StepDefinition.java b/core/src/main/java/io/cucumber/core/event/StepDefinition.java
new file mode 100644
index 0000000000..3f6353a619
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/event/StepDefinition.java
@@ -0,0 +1,22 @@
+package io.cucumber.core.event;
+
+
+import org.apiguardian.api.API;
+
+@API(status = API.Status.STABLE)
+public interface StepDefinition {
+
+ /**
+ * The source line where the step definition is defined.
+ * Example: com/example/app/Cucumber.test():42
+ *
+ * @param detail true if extra detailed location information should be included.
+ * @return The source line of the step definition.
+ */
+ String getLocation(boolean detail);
+
+ /**
+ * @return the pattern associated with this instance. Used for error reporting only.
+ */
+ String getPattern();
+}
diff --git a/core/src/main/java/io/cucumber/core/event/TestCase.java b/core/src/main/java/io/cucumber/core/event/TestCase.java
new file mode 100644
index 0000000000..eb3a65821e
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/event/TestCase.java
@@ -0,0 +1,26 @@
+package io.cucumber.core.event;
+
+import org.apiguardian.api.API;
+
+import java.util.List;
+
+@API(status = API.Status.STABLE)
+public interface TestCase {
+
+ /**
+ * @return the line in the feature file of the Scenario. If this is a Scenario
+ * from Scenario Outlines this wil return the line of the example row in
+ * the Scenario Outline.
+ */
+ Integer getLine();
+
+ String getName();
+
+ String getScenarioDesignation();
+
+ List getTags();
+
+ List getTestSteps();
+
+ String getUri();
+}
diff --git a/core/src/main/java/io/cucumber/core/event/TestCaseEvent.java b/core/src/main/java/io/cucumber/core/event/TestCaseEvent.java
new file mode 100644
index 0000000000..df8fe28054
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/event/TestCaseEvent.java
@@ -0,0 +1,21 @@
+package io.cucumber.core.event;
+
+import org.apiguardian.api.API;
+
+import java.time.Instant;
+import java.util.Objects;
+
+@API(status = API.Status.STABLE)
+public abstract class TestCaseEvent extends TimeStampedEvent {
+
+ private final TestCase testCase;
+
+ TestCaseEvent(Instant timeInstant, TestCase testCase) {
+ super(timeInstant);
+ this.testCase = Objects.requireNonNull(testCase);
+ }
+
+ public TestCase getTestCase() {
+ return testCase;
+ }
+}
diff --git a/core/src/main/java/io/cucumber/core/event/TestCaseFinished.java b/core/src/main/java/io/cucumber/core/event/TestCaseFinished.java
new file mode 100644
index 0000000000..07b4c65a20
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/event/TestCaseFinished.java
@@ -0,0 +1,27 @@
+package io.cucumber.core.event;
+
+import org.apiguardian.api.API;
+
+import java.time.Instant;
+import java.util.Objects;
+
+@API(status = API.Status.STABLE)
+public final class TestCaseFinished extends TestCaseEvent {
+ private final Result result;
+ private final TestCase testCase;
+
+ public TestCaseFinished(Instant timeInstant, TestCase testCase, Result result) {
+ super(timeInstant, testCase);
+ this.testCase = Objects.requireNonNull(testCase);
+ this.result = Objects.requireNonNull(result);
+ }
+
+ public Result getResult() {
+ return result;
+ }
+
+ @Override
+ public TestCase getTestCase() {
+ return testCase;
+ }
+}
diff --git a/core/src/main/java/io/cucumber/core/event/TestCaseStarted.java b/core/src/main/java/io/cucumber/core/event/TestCaseStarted.java
new file mode 100644
index 0000000000..c6c5b28ee0
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/event/TestCaseStarted.java
@@ -0,0 +1,21 @@
+package io.cucumber.core.event;
+
+import org.apiguardian.api.API;
+
+import java.time.Instant;
+import java.util.Objects;
+
+@API(status = API.Status.STABLE)
+public final class TestCaseStarted extends TestCaseEvent {
+ private final TestCase testCase;
+
+ public TestCaseStarted(Instant timeInstant, TestCase testCase) {
+ super(timeInstant, testCase);
+ this.testCase = Objects.requireNonNull(testCase);
+ }
+
+ @Override
+ public TestCase getTestCase() {
+ return testCase;
+ }
+}
diff --git a/core/src/main/java/io/cucumber/core/event/TestRunFinished.java b/core/src/main/java/io/cucumber/core/event/TestRunFinished.java
new file mode 100644
index 0000000000..14e6c02985
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/event/TestRunFinished.java
@@ -0,0 +1,13 @@
+package io.cucumber.core.event;
+
+import org.apiguardian.api.API;
+
+import java.time.Instant;
+
+@API(status = API.Status.STABLE)
+public final class TestRunFinished extends TimeStampedEvent {
+
+ public TestRunFinished(Instant timeInstant) {
+ super(timeInstant);
+ }
+}
diff --git a/core/src/main/java/io/cucumber/core/event/TestRunStarted.java b/core/src/main/java/io/cucumber/core/event/TestRunStarted.java
new file mode 100644
index 0000000000..ab7ce723af
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/event/TestRunStarted.java
@@ -0,0 +1,13 @@
+package io.cucumber.core.event;
+
+import org.apiguardian.api.API;
+
+import java.time.Instant;
+
+@API(status = API.Status.STABLE)
+public final class TestRunStarted extends TimeStampedEvent {
+
+ public TestRunStarted(Instant timeInstant) {
+ super(timeInstant);
+ }
+}
diff --git a/core/src/main/java/io/cucumber/core/event/TestSourceRead.java b/core/src/main/java/io/cucumber/core/event/TestSourceRead.java
new file mode 100644
index 0000000000..7df4734922
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/event/TestSourceRead.java
@@ -0,0 +1,26 @@
+package io.cucumber.core.event;
+
+import org.apiguardian.api.API;
+
+import java.time.Instant;
+import java.util.Objects;
+
+@API(status = API.Status.STABLE)
+public final class TestSourceRead extends TimeStampedEvent {
+ private final String uri;
+ private final String source;
+
+ public TestSourceRead(Instant timeInstant, String uri, String source) {
+ super(timeInstant);
+ this.uri = Objects.requireNonNull(uri);
+ this.source = Objects.requireNonNull(source);
+ }
+
+ public String getSource() {
+ return source;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+}
diff --git a/core/src/main/java/cucumber/api/TestStep.java b/core/src/main/java/io/cucumber/core/event/TestStep.java
similarity index 70%
rename from core/src/main/java/cucumber/api/TestStep.java
rename to core/src/main/java/io/cucumber/core/event/TestStep.java
index fcd3eb05e1..fdc9bff095 100644
--- a/core/src/main/java/cucumber/api/TestStep.java
+++ b/core/src/main/java/io/cucumber/core/event/TestStep.java
@@ -1,12 +1,16 @@
-package cucumber.api;
+package io.cucumber.core.event;
+
+import org.apiguardian.api.API;
/**
* A test step can either represent the execution of a hook
* or a pickle step. Each step is tied to some glue code.
*
- * @see cucumber.api.event.TestCaseStarted
- * @see cucumber.api.event.TestCaseFinished
+ * @see TestCaseStarted
+ * @see TestCaseFinished
*/
+
+@API(status = API.Status.STABLE)
public interface TestStep {
/**
diff --git a/core/src/main/java/io/cucumber/core/event/TestStepFinished.java b/core/src/main/java/io/cucumber/core/event/TestStepFinished.java
new file mode 100644
index 0000000000..13422104e1
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/event/TestStepFinished.java
@@ -0,0 +1,43 @@
+package io.cucumber.core.event;
+
+import org.apiguardian.api.API;
+
+import java.time.Instant;
+import java.util.Objects;
+
+/**
+ * A test step finished event is broadcast when ever a step finishes.
+ *
+ * A step can either be a {@link PickleStepTestStep} or a
+ * {@link HookTestStep} depending on what step was executed.
+ *
+ * Each test step finished event is followed by an matching
+ * {@link TestStepStarted} event for the same step.The order in which
+ * these events may be expected is:
+ *
+ * [before hook,]* [[before step hook,]* test step, [after step hook,]*]+, [after hook,]*
+ *
+ *
+ * @see PickleStepTestStep
+ * @see HookTestStep
+ */
+
+@API(status = API.Status.STABLE)
+public final class TestStepFinished extends TestCaseEvent {
+ private final TestStep testStep;
+ private final Result result;
+
+ public TestStepFinished(Instant timeInstant, TestCase testCase, TestStep testStep, Result result) {
+ super(timeInstant, testCase);
+ this.testStep = Objects.requireNonNull(testStep);
+ this.result = Objects.requireNonNull(result);
+ }
+
+ public Result getResult() {
+ return result;
+ }
+
+ public TestStep getTestStep() {
+ return testStep;
+ }
+}
diff --git a/core/src/main/java/cucumber/api/event/TestStepStarted.java b/core/src/main/java/io/cucumber/core/event/TestStepStarted.java
similarity index 53%
rename from core/src/main/java/cucumber/api/event/TestStepStarted.java
rename to core/src/main/java/io/cucumber/core/event/TestStepStarted.java
index 6392eec7b2..c6d47f0ce9 100644
--- a/core/src/main/java/cucumber/api/event/TestStepStarted.java
+++ b/core/src/main/java/io/cucumber/core/event/TestStepStarted.java
@@ -1,9 +1,9 @@
-package cucumber.api.event;
+package io.cucumber.core.event;
-import cucumber.api.HookTestStep;
-import cucumber.api.PickleStepTestStep;
-import cucumber.api.TestCase;
-import cucumber.api.TestStep;
+import org.apiguardian.api.API;
+
+import java.time.Instant;
+import java.util.Objects;
/**
* A test step started event is broadcast when ever a step starts.
@@ -22,17 +22,17 @@
* @see PickleStepTestStep
* @see HookTestStep
*/
+
+@API(status = API.Status.STABLE)
public final class TestStepStarted extends TestCaseEvent {
- public final TestStep testStep;
+ private final TestStep testStep;
- @Deprecated
- public TestStepStarted(Long timeStamp, TestCase testCase, TestStep testStep) {
- this(timeStamp, 0, testCase, testStep);
+ public TestStepStarted(Instant timeInstant, TestCase testCase, TestStep testStep) {
+ super(timeInstant, testCase);
+ this.testStep = Objects.requireNonNull(testStep);
}
- public TestStepStarted(Long timeStamp, long timeStampMillis, TestCase testCase, TestStep testStep) {
- super(timeStamp, timeStampMillis, testCase);
- this.testStep = testStep;
+ public TestStep getTestStep() {
+ return testStep;
}
-
}
diff --git a/core/src/main/java/io/cucumber/core/event/TimeStampedEvent.java b/core/src/main/java/io/cucumber/core/event/TimeStampedEvent.java
new file mode 100644
index 0000000000..b51fc8b9dc
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/event/TimeStampedEvent.java
@@ -0,0 +1,22 @@
+package io.cucumber.core.event;
+
+import java.time.Instant;
+import java.util.Objects;
+
+
+abstract class TimeStampedEvent implements Event {
+
+ private final Instant instant;
+
+ TimeStampedEvent(Instant timeInstant) {
+ this.instant = Objects.requireNonNull(timeInstant);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Instant getInstant() {
+ return instant;
+ }
+}
diff --git a/core/src/main/java/io/cucumber/core/event/WriteEvent.java b/core/src/main/java/io/cucumber/core/event/WriteEvent.java
new file mode 100644
index 0000000000..e3fbab800d
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/event/WriteEvent.java
@@ -0,0 +1,20 @@
+package io.cucumber.core.event;
+
+import org.apiguardian.api.API;
+
+import java.time.Instant;
+import java.util.Objects;
+
+@API(status = API.Status.STABLE)
+public final class WriteEvent extends TestCaseEvent {
+ private final String text;
+
+ public WriteEvent(Instant timeInstant, TestCase testCase, String text) {
+ super(timeInstant, testCase);
+ this.text = Objects.requireNonNull(text);
+ }
+
+ public String getText() {
+ return text;
+ }
+}
diff --git a/core/src/main/java/cucumber/runner/AbstractEventBus.java b/core/src/main/java/io/cucumber/core/eventbus/AbstractEventBus.java
similarity index 53%
rename from core/src/main/java/cucumber/runner/AbstractEventBus.java
rename to core/src/main/java/io/cucumber/core/eventbus/AbstractEventBus.java
index 391b2285e7..c231ec72a4 100644
--- a/core/src/main/java/cucumber/runner/AbstractEventBus.java
+++ b/core/src/main/java/io/cucumber/core/eventbus/AbstractEventBus.java
@@ -1,8 +1,8 @@
-package cucumber.runner;
+package io.cucumber.core.eventbus;
-import cucumber.api.event.Event;
+import io.cucumber.core.event.Event;
-abstract class AbstractEventBus extends AbstractEventPublisher implements EventBus {
+public abstract class AbstractEventBus extends AbstractEventPublisher implements EventBus {
@Override
public void send(Event event) {
diff --git a/core/src/main/java/cucumber/runner/AbstractEventPublisher.java b/core/src/main/java/io/cucumber/core/eventbus/AbstractEventPublisher.java
similarity index 86%
rename from core/src/main/java/cucumber/runner/AbstractEventPublisher.java
rename to core/src/main/java/io/cucumber/core/eventbus/AbstractEventPublisher.java
index f3661b6ebb..b37a703cc6 100644
--- a/core/src/main/java/cucumber/runner/AbstractEventPublisher.java
+++ b/core/src/main/java/io/cucumber/core/eventbus/AbstractEventPublisher.java
@@ -1,15 +1,15 @@
-package cucumber.runner;
+package io.cucumber.core.eventbus;
-import cucumber.api.event.Event;
-import cucumber.api.event.EventHandler;
-import cucumber.api.event.EventPublisher;
+import io.cucumber.core.event.Event;
+import io.cucumber.core.event.EventHandler;
+import io.cucumber.core.event.EventPublisher;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-class AbstractEventPublisher implements EventPublisher {
+public abstract class AbstractEventPublisher implements EventPublisher {
protected Map, List> handlers = new HashMap, List>();
@Override
diff --git a/core/src/main/java/io/cucumber/core/eventbus/EventBus.java b/core/src/main/java/io/cucumber/core/eventbus/EventBus.java
new file mode 100644
index 0000000000..c16bf48169
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/eventbus/EventBus.java
@@ -0,0 +1,16 @@
+package io.cucumber.core.eventbus;
+
+import java.time.Instant;
+
+import io.cucumber.core.event.Event;
+import io.cucumber.core.event.EventPublisher;
+
+public interface EventBus extends EventPublisher {
+
+ Instant getInstant();
+
+ void send(Event event);
+
+ void sendAll(Iterable queue);
+
+}
diff --git a/core/src/main/java/cucumber/runtime/CompositeCucumberException.java b/core/src/main/java/io/cucumber/core/exception/CompositeCucumberException.java
similarity index 78%
rename from core/src/main/java/cucumber/runtime/CompositeCucumberException.java
rename to core/src/main/java/io/cucumber/core/exception/CompositeCucumberException.java
index c046234df6..4a168b569a 100644
--- a/core/src/main/java/cucumber/runtime/CompositeCucumberException.java
+++ b/core/src/main/java/io/cucumber/core/exception/CompositeCucumberException.java
@@ -1,12 +1,12 @@
-package cucumber.runtime;
+package io.cucumber.core.exception;
import java.util.Collections;
import java.util.List;
-class CompositeCucumberException extends CucumberException {
+public class CompositeCucumberException extends CucumberException {
private final List causes;
- CompositeCucumberException(List causes) {
+ public CompositeCucumberException(List causes) {
super(String.format("There were %d exceptions:", causes.size()));
this.causes = causes;
}
diff --git a/core/src/main/java/cucumber/runtime/CucumberException.java b/core/src/main/java/io/cucumber/core/exception/CucumberException.java
similarity index 89%
rename from core/src/main/java/cucumber/runtime/CucumberException.java
rename to core/src/main/java/io/cucumber/core/exception/CucumberException.java
index 4ce81aa33a..07e58a1957 100644
--- a/core/src/main/java/cucumber/runtime/CucumberException.java
+++ b/core/src/main/java/io/cucumber/core/exception/CucumberException.java
@@ -1,4 +1,4 @@
-package cucumber.runtime;
+package io.cucumber.core.exception;
public class CucumberException extends RuntimeException {
public CucumberException(String message) {
diff --git a/core/src/main/java/cucumber/runtime/model/CucumberFeature.java b/core/src/main/java/io/cucumber/core/feature/CucumberFeature.java
similarity index 83%
rename from core/src/main/java/cucumber/runtime/model/CucumberFeature.java
rename to core/src/main/java/io/cucumber/core/feature/CucumberFeature.java
index c3990b0bf8..a51e79f412 100644
--- a/core/src/main/java/cucumber/runtime/model/CucumberFeature.java
+++ b/core/src/main/java/io/cucumber/core/feature/CucumberFeature.java
@@ -1,7 +1,5 @@
-package cucumber.runtime.model;
+package io.cucumber.core.feature;
-import cucumber.api.event.TestSourceRead;
-import cucumber.runner.EventBus;
import gherkin.ast.GherkinDocument;
import gherkin.events.PickleEvent;
@@ -40,11 +38,7 @@ public URI getUri() {
return uri;
}
- public void sendTestSourceRead(EventBus bus) {
- bus.send(new TestSourceRead(bus.getTime(), bus.getTimeMillis(), getUri().toString(), gherkinSource));
- }
-
- String getSource() {
+ public String getSource() {
return gherkinSource;
}
diff --git a/core/src/main/java/cucumber/util/Encoding.java b/core/src/main/java/io/cucumber/core/feature/Encoding.java
similarity index 52%
rename from core/src/main/java/cucumber/util/Encoding.java
rename to core/src/main/java/io/cucumber/core/feature/Encoding.java
index 8fe0b94140..463fbfda99 100644
--- a/core/src/main/java/cucumber/util/Encoding.java
+++ b/core/src/main/java/io/cucumber/core/feature/Encoding.java
@@ -1,36 +1,45 @@
-package cucumber.util;
+package io.cucumber.core.feature;
-import cucumber.runtime.io.Resource;
+import io.cucumber.core.io.Resource;
+import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Locale.ROOT;
/**
* Utilities for reading the encoding of a file.
*/
-public class Encoding {
+final class Encoding {
private static final Pattern COMMENT_OR_EMPTY_LINE_PATTERN = Pattern.compile("^\\s*#|^\\s*$");
private static final Pattern ENCODING_PATTERN = Pattern.compile("^\\s*#\\s*encoding\\s*:\\s*([0-9a-zA-Z\\-]+)", Pattern.CASE_INSENSITIVE);
- public static final String DEFAULT_ENCODING = "UTF-8";
+ private static final String DEFAULT_ENCODING = UTF_8.name();
private static final String UTF_8_BOM = "\uFEFF";
- public static String readFile(Resource resource) throws RuntimeException, IOException {
- String source = FixJava.readReader(new InputStreamReader(resource.getInputStream(), DEFAULT_ENCODING));
- // Remove UTF8 BOM encoded in first bytes
- if (source.startsWith(UTF_8_BOM)) {
- source = source.replaceFirst(UTF_8_BOM, "");
- }
+ static String readFile(Resource resource) throws RuntimeException, IOException {
+ String source = read(resource, DEFAULT_ENCODING);
+ // Remove UTF8 BOM encoded in first bytes
+ if (source.startsWith(UTF_8_BOM)) {
+ source = source.replaceFirst(UTF_8_BOM, "");
+ }
String enc = encoding(source);
- if(!enc.equals(DEFAULT_ENCODING)) {
- source = FixJava.readReader(new InputStreamReader(resource.getInputStream(), enc));
+ if (!enc.equals(DEFAULT_ENCODING)) {
+ source = read(resource, enc);
}
return source;
}
+ private static String read(Resource resource, String encoding) throws IOException {
+ try(BufferedReader br = new BufferedReader(new InputStreamReader(resource.getInputStream(), encoding))){
+ return br.lines().collect(Collectors.joining(System.lineSeparator()));
+ }
+ }
+
private static String encoding(String source) {
String encoding = DEFAULT_ENCODING;
for (String line : source.split("\\n")) {
diff --git a/core/src/main/java/cucumber/runtime/model/FeatureBuilder.java b/core/src/main/java/io/cucumber/core/feature/FeatureBuilder.java
similarity index 91%
rename from core/src/main/java/cucumber/runtime/model/FeatureBuilder.java
rename to core/src/main/java/io/cucumber/core/feature/FeatureBuilder.java
index caf45d871e..b128ea6f5f 100644
--- a/core/src/main/java/cucumber/runtime/model/FeatureBuilder.java
+++ b/core/src/main/java/io/cucumber/core/feature/FeatureBuilder.java
@@ -1,6 +1,6 @@
-package cucumber.runtime.model;
+package io.cucumber.core.feature;
-import cucumber.runtime.io.Resource;
+import io.cucumber.core.io.Resource;
import io.cucumber.core.logging.Logger;
import io.cucumber.core.logging.LoggerFactory;
@@ -10,7 +10,7 @@
import java.util.List;
import java.util.Map;
-public class FeatureBuilder {
+final class FeatureBuilder {
private final Logger log = LoggerFactory.getLogger(FeatureBuilder.class);
private final Map sourceToFeature = new HashMap<>();
diff --git a/core/src/main/java/io/cucumber/core/model/FeatureIdentifier.java b/core/src/main/java/io/cucumber/core/feature/FeatureIdentifier.java
similarity index 96%
rename from core/src/main/java/io/cucumber/core/model/FeatureIdentifier.java
rename to core/src/main/java/io/cucumber/core/feature/FeatureIdentifier.java
index acf432e4b5..9d782ba67c 100644
--- a/core/src/main/java/io/cucumber/core/model/FeatureIdentifier.java
+++ b/core/src/main/java/io/cucumber/core/feature/FeatureIdentifier.java
@@ -1,5 +1,4 @@
-package io.cucumber.core.model;
-
+package io.cucumber.core.feature;
import java.net.URI;
/**
diff --git a/core/src/main/java/cucumber/runtime/model/FeatureLoader.java b/core/src/main/java/io/cucumber/core/feature/FeatureLoader.java
similarity index 87%
rename from core/src/main/java/cucumber/runtime/model/FeatureLoader.java
rename to core/src/main/java/io/cucumber/core/feature/FeatureLoader.java
index 24fead395f..1f0636ab40 100644
--- a/core/src/main/java/cucumber/runtime/model/FeatureLoader.java
+++ b/core/src/main/java/io/cucumber/core/feature/FeatureLoader.java
@@ -1,8 +1,7 @@
-package cucumber.runtime.model;
+package io.cucumber.core.feature;
-import cucumber.runtime.io.Resource;
-import cucumber.runtime.io.ResourceLoader;
-import io.cucumber.core.model.FeatureIdentifier;
+import io.cucumber.core.io.Resource;
+import io.cucumber.core.io.ResourceLoader;
import java.net.URI;
import java.util.Iterator;
diff --git a/core/src/main/java/cucumber/runtime/model/FeatureParser.java b/core/src/main/java/io/cucumber/core/feature/FeatureParser.java
similarity index 93%
rename from core/src/main/java/cucumber/runtime/model/FeatureParser.java
rename to core/src/main/java/io/cucumber/core/feature/FeatureParser.java
index 0de23a4893..8173f4ec43 100644
--- a/core/src/main/java/cucumber/runtime/model/FeatureParser.java
+++ b/core/src/main/java/io/cucumber/core/feature/FeatureParser.java
@@ -1,8 +1,5 @@
-package cucumber.runtime.model;
+package io.cucumber.core.feature;
-import cucumber.runtime.CucumberException;
-import cucumber.runtime.io.Resource;
-import cucumber.util.Encoding;
import gherkin.AstBuilder;
import gherkin.Parser;
import gherkin.ParserException;
@@ -11,6 +8,8 @@
import gherkin.events.PickleEvent;
import gherkin.pickles.Compiler;
import gherkin.pickles.Pickle;
+import io.cucumber.core.exception.CucumberException;
+import io.cucumber.core.io.Resource;
import java.io.IOException;
import java.net.URI;
diff --git a/core/src/main/java/io/cucumber/core/model/FeaturePath.java b/core/src/main/java/io/cucumber/core/feature/FeaturePath.java
similarity index 95%
rename from core/src/main/java/io/cucumber/core/model/FeaturePath.java
rename to core/src/main/java/io/cucumber/core/feature/FeaturePath.java
index 719a2605c1..24824d6e19 100644
--- a/core/src/main/java/io/cucumber/core/model/FeaturePath.java
+++ b/core/src/main/java/io/cucumber/core/feature/FeaturePath.java
@@ -1,15 +1,14 @@
-package io.cucumber.core.model;
+package io.cucumber.core.feature;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Locale;
-import static io.cucumber.core.model.Classpath.CLASSPATH_SCHEME;
-import static io.cucumber.core.model.Classpath.CLASSPATH_SCHEME_PREFIX;
+import static io.cucumber.core.io.Classpath.CLASSPATH_SCHEME;
+import static io.cucumber.core.io.Classpath.CLASSPATH_SCHEME_PREFIX;
import static java.util.Objects.requireNonNull;
-
/**
* A feature path is a URI to a single feature file or directory of features.
*
diff --git a/core/src/main/java/io/cucumber/core/model/FeatureWithLines.java b/core/src/main/java/io/cucumber/core/feature/FeatureWithLines.java
similarity index 97%
rename from core/src/main/java/io/cucumber/core/model/FeatureWithLines.java
rename to core/src/main/java/io/cucumber/core/feature/FeatureWithLines.java
index ac2139ab04..021d3037ab 100644
--- a/core/src/main/java/io/cucumber/core/model/FeatureWithLines.java
+++ b/core/src/main/java/io/cucumber/core/feature/FeatureWithLines.java
@@ -1,4 +1,4 @@
-package io.cucumber.core.model;
+package io.cucumber.core.feature;
import java.io.Serializable;
import java.net.URI;
@@ -23,7 +23,7 @@
public class FeatureWithLines implements Serializable {
private static final long serialVersionUID = 20190126L;
private static final Pattern FEATURE_COLON_LINE_PATTERN = Pattern.compile("^(.*?):([\\d:]+)$");
- private static final String INVALID_PATH_MESSAGE = " is not valid. Try URI[:LINE]*";
+ private static final String INVALID_PATH_MESSAGE = " is not valid. Try /.feature[:LINE]*";
private final URI uri;
private final SortedSet lines;
diff --git a/core/src/main/java/io/cucumber/core/model/GluePath.java b/core/src/main/java/io/cucumber/core/feature/GluePath.java
similarity index 95%
rename from core/src/main/java/io/cucumber/core/model/GluePath.java
rename to core/src/main/java/io/cucumber/core/feature/GluePath.java
index 2e13c30f13..1314fe8c66 100644
--- a/core/src/main/java/io/cucumber/core/model/GluePath.java
+++ b/core/src/main/java/io/cucumber/core/feature/GluePath.java
@@ -1,11 +1,9 @@
-package io.cucumber.core.model;
+package io.cucumber.core.feature;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
-import static io.cucumber.core.model.Classpath.CLASSPATH_SCHEME;
-import static io.cucumber.core.model.Classpath.CLASSPATH_SCHEME_PREFIX;
import static java.lang.Character.isJavaIdentifierPart;
import static java.lang.Character.isJavaIdentifierStart;
import static java.util.Objects.requireNonNull;
@@ -24,6 +22,9 @@
*/
public class GluePath {
+ private static final String CLASSPATH_SCHEME = "classpath";
+ private static final String CLASSPATH_SCHEME_PREFIX = CLASSPATH_SCHEME + ":";
+
private GluePath(){
}
diff --git a/core/src/main/java/io/cucumber/core/options/FeatureOptions.java b/core/src/main/java/io/cucumber/core/feature/Options.java
similarity index 54%
rename from core/src/main/java/io/cucumber/core/options/FeatureOptions.java
rename to core/src/main/java/io/cucumber/core/feature/Options.java
index 47450f625c..eeb5fc29c9 100644
--- a/core/src/main/java/io/cucumber/core/options/FeatureOptions.java
+++ b/core/src/main/java/io/cucumber/core/feature/Options.java
@@ -1,8 +1,8 @@
-package io.cucumber.core.options;
+package io.cucumber.core.feature;
import java.net.URI;
import java.util.List;
-public interface FeatureOptions {
+public interface Options {
List getFeaturePaths();
}
diff --git a/core/src/main/java/io/cucumber/core/model/RerunLoader.java b/core/src/main/java/io/cucumber/core/feature/RerunLoader.java
similarity index 77%
rename from core/src/main/java/io/cucumber/core/model/RerunLoader.java
rename to core/src/main/java/io/cucumber/core/feature/RerunLoader.java
index 908a5dc031..b0c00670ab 100644
--- a/core/src/main/java/io/cucumber/core/model/RerunLoader.java
+++ b/core/src/main/java/io/cucumber/core/feature/RerunLoader.java
@@ -1,10 +1,10 @@
-package io.cucumber.core.model;
+package io.cucumber.core.feature;
-import cucumber.runtime.CucumberException;
-import cucumber.runtime.io.Resource;
-import cucumber.runtime.io.ResourceLoader;
-import cucumber.util.FixJava;
+import io.cucumber.core.exception.CucumberException;
+import io.cucumber.core.io.Resource;
+import io.cucumber.core.io.ResourceLoader;
+import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
@@ -12,6 +12,7 @@
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
public class RerunLoader {
private static final Pattern RERUN_PATH_SPECIFICATION = Pattern.compile("(?m:^| |)(.*?\\.feature(?:(?::\\d+)*))");
@@ -43,8 +44,8 @@ public List load(URI rerunPath) {
}
private static String read(Resource resource) {
- try {
- return FixJava.readReader(new InputStreamReader(resource.getInputStream()));
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(resource.getInputStream()))) {
+ return br.lines().collect(Collectors.joining(System.lineSeparator()));
} catch (IOException e) {
throw new CucumberException("Failed to read resource:" + resource.getPath(), e);
}
diff --git a/core/src/main/java/cucumber/runtime/filter/Filters.java b/core/src/main/java/io/cucumber/core/filter/Filters.java
similarity index 64%
rename from core/src/main/java/cucumber/runtime/filter/Filters.java
rename to core/src/main/java/io/cucumber/core/filter/Filters.java
index d78d1f8bfc..339801c194 100644
--- a/core/src/main/java/cucumber/runtime/filter/Filters.java
+++ b/core/src/main/java/io/cucumber/core/filter/Filters.java
@@ -1,7 +1,6 @@
-package cucumber.runtime.filter;
+package io.cucumber.core.filter;
import gherkin.events.PickleEvent;
-import io.cucumber.core.options.FilterOptions;
import java.net.URI;
import java.util.ArrayList;
@@ -10,29 +9,28 @@
import java.util.Map;
import java.util.regex.Pattern;
-public class Filters {
+public final class Filters {
private final List filters;
-
- private int count;
- public Filters(FilterOptions filterOptions) {
+ private int count;
+ public Filters(Options options) {
filters = new ArrayList<>();
- List tagFilters = filterOptions.getTagFilters();
- if (!tagFilters.isEmpty()) {
- this.filters.add(new TagPredicate(tagFilters));
+ List tagExpressions = options.getTagExpressions();
+ if (!tagExpressions.isEmpty()) {
+ this.filters.add(new TagPredicate(tagExpressions));
}
- List nameFilters = filterOptions.getNameFilters();
+ List nameFilters = options.getNameFilters();
if (!nameFilters.isEmpty()) {
this.filters.add(new NamePredicate(nameFilters));
}
- Map> lineFilters = filterOptions.getLineFilters();
+ Map> lineFilters = options.getLineFilters();
if (!lineFilters.isEmpty()) {
this.filters.add(new LinePredicate(lineFilters));
}
-
- this.count = filterOptions.getLimitCount();
+
+ this.count = options.getLimitCount();
}
public boolean matchesFilters(PickleEvent pickleEvent) {
@@ -43,7 +41,7 @@ public boolean matchesFilters(PickleEvent pickleEvent) {
}
return true;
}
-
+
public List limitPickleEvents(List pickleEvents) {
if (count > pickleEvents.size() || count < 1) {
return pickleEvents;
diff --git a/core/src/main/java/cucumber/runtime/filter/LinePredicate.java b/core/src/main/java/io/cucumber/core/filter/LinePredicate.java
similarity index 96%
rename from core/src/main/java/cucumber/runtime/filter/LinePredicate.java
rename to core/src/main/java/io/cucumber/core/filter/LinePredicate.java
index 36560a607b..df43f1c5c3 100644
--- a/core/src/main/java/cucumber/runtime/filter/LinePredicate.java
+++ b/core/src/main/java/io/cucumber/core/filter/LinePredicate.java
@@ -1,4 +1,4 @@
-package cucumber.runtime.filter;
+package io.cucumber.core.filter;
import gherkin.events.PickleEvent;
import gherkin.pickles.PickleLocation;
diff --git a/core/src/main/java/cucumber/runtime/filter/NamePredicate.java b/core/src/main/java/io/cucumber/core/filter/NamePredicate.java
similarity index 85%
rename from core/src/main/java/cucumber/runtime/filter/NamePredicate.java
rename to core/src/main/java/io/cucumber/core/filter/NamePredicate.java
index b69ca5577d..37c1aff1ac 100644
--- a/core/src/main/java/cucumber/runtime/filter/NamePredicate.java
+++ b/core/src/main/java/io/cucumber/core/filter/NamePredicate.java
@@ -1,11 +1,11 @@
-package cucumber.runtime.filter;
+package io.cucumber.core.filter;
import gherkin.events.PickleEvent;
import java.util.List;
import java.util.regex.Pattern;
-class NamePredicate implements PicklePredicate {
+final class NamePredicate implements PicklePredicate {
private List patterns;
NamePredicate(List patterns) {
diff --git a/core/src/main/java/io/cucumber/core/options/FilterOptions.java b/core/src/main/java/io/cucumber/core/filter/Options.java
similarity index 68%
rename from core/src/main/java/io/cucumber/core/options/FilterOptions.java
rename to core/src/main/java/io/cucumber/core/filter/Options.java
index 3a95be2ac4..e44a1ecd7a 100644
--- a/core/src/main/java/io/cucumber/core/options/FilterOptions.java
+++ b/core/src/main/java/io/cucumber/core/filter/Options.java
@@ -1,4 +1,4 @@
-package io.cucumber.core.options;
+package io.cucumber.core.filter;
import java.net.URI;
import java.util.List;
@@ -6,12 +6,12 @@
import java.util.Set;
import java.util.regex.Pattern;
-public interface FilterOptions {
- List getNameFilters();
+public interface Options {
+ List getTagExpressions();
- List getTagFilters();
+ List getNameFilters();
Map> getLineFilters();
-
+
int getLimitCount();
}
diff --git a/core/src/main/java/cucumber/runtime/filter/PicklePredicate.java b/core/src/main/java/io/cucumber/core/filter/PicklePredicate.java
similarity index 77%
rename from core/src/main/java/cucumber/runtime/filter/PicklePredicate.java
rename to core/src/main/java/io/cucumber/core/filter/PicklePredicate.java
index e4c2a362df..0319fe9a16 100644
--- a/core/src/main/java/cucumber/runtime/filter/PicklePredicate.java
+++ b/core/src/main/java/io/cucumber/core/filter/PicklePredicate.java
@@ -1,4 +1,4 @@
-package cucumber.runtime.filter;
+package io.cucumber.core.filter;
import gherkin.events.PickleEvent;
diff --git a/core/src/main/java/cucumber/runtime/filter/TagPredicate.java b/core/src/main/java/io/cucumber/core/filter/TagPredicate.java
similarity index 52%
rename from core/src/main/java/cucumber/runtime/filter/TagPredicate.java
rename to core/src/main/java/io/cucumber/core/filter/TagPredicate.java
index 9f1bcfe7e2..285eaad676 100644
--- a/core/src/main/java/cucumber/runtime/filter/TagPredicate.java
+++ b/core/src/main/java/io/cucumber/core/filter/TagPredicate.java
@@ -1,4 +1,4 @@
-package cucumber.runtime.filter;
+package io.cucumber.core.filter;
import gherkin.events.PickleEvent;
import gherkin.pickles.PickleTag;
@@ -9,24 +9,24 @@
import java.util.Collection;
import java.util.List;
-import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
-public class TagPredicate implements PicklePredicate {
- private final List expressions = new ArrayList();
- private final List oldStyleExpressions = new ArrayList();
+public final class TagPredicate implements PicklePredicate {
+ private final List expressions = new ArrayList<>();
- public TagPredicate(List tagExpressions) {
+ public TagPredicate(String tagExpression) {
+ this(tagExpression.isEmpty() ? emptyList() : singletonList(tagExpression));
+ }
+
+ TagPredicate(List tagExpressions) {
if (tagExpressions == null) {
return;
}
TagExpressionParser parser = new TagExpressionParser();
for (String tagExpression : tagExpressions) {
- if (TagExpressionOld.isOldTagExpression(tagExpression)) {
- oldStyleExpressions.add(new TagExpressionOld(asList(tagExpression)));
- } else {
- expressions.add(parser.parse(tagExpression));
- }
+ expressions.add(parser.parse(tagExpression));
}
}
@@ -36,12 +36,11 @@ public boolean apply(PickleEvent pickleEvent) {
}
public boolean apply(Collection pickleTags) {
- for (TagExpressionOld oldStyleExpression : oldStyleExpressions) {
- if (!oldStyleExpression.evaluate(pickleTags)) {
- return false;
- }
+ if (expressions.isEmpty()) {
+ return true;
}
- List tags = new ArrayList();
+
+ List tags = new ArrayList<>();
for (PickleTag pickleTag : pickleTags) {
tags.add(pickleTag.getName());
}
diff --git a/core/src/main/java/cucumber/runtime/ClassFinder.java b/core/src/main/java/io/cucumber/core/io/ClassFinder.java
similarity index 90%
rename from core/src/main/java/cucumber/runtime/ClassFinder.java
rename to core/src/main/java/io/cucumber/core/io/ClassFinder.java
index a023adf286..f130020ba1 100644
--- a/core/src/main/java/cucumber/runtime/ClassFinder.java
+++ b/core/src/main/java/io/cucumber/core/io/ClassFinder.java
@@ -1,4 +1,4 @@
-package cucumber.runtime;
+package io.cucumber.core.io;
import java.net.URI;
import java.util.Collection;
diff --git a/core/src/main/java/io/cucumber/core/model/Classpath.java b/core/src/main/java/io/cucumber/core/io/Classpath.java
similarity index 90%
rename from core/src/main/java/io/cucumber/core/model/Classpath.java
rename to core/src/main/java/io/cucumber/core/io/Classpath.java
index 986bacef8b..1ab1999d4d 100644
--- a/core/src/main/java/io/cucumber/core/model/Classpath.java
+++ b/core/src/main/java/io/cucumber/core/io/Classpath.java
@@ -1,4 +1,4 @@
-package io.cucumber.core.model;
+package io.cucumber.core.io;
import java.net.URI;
@@ -17,7 +17,7 @@ private Classpath() {
* @param uri to resource
* @return resource name
*/
- public static String resourceName(URI uri) {
+ static String resourceName(URI uri) {
if (!CLASSPATH_SCHEME.equals(uri.getScheme())) {
throw new IllegalArgumentException("uri must have classpath scheme " + uri);
}
diff --git a/core/src/main/java/cucumber/runtime/io/ClasspathResourceIterable.java b/core/src/main/java/io/cucumber/core/io/ClasspathResourceIterable.java
similarity index 85%
rename from core/src/main/java/cucumber/runtime/io/ClasspathResourceIterable.java
rename to core/src/main/java/io/cucumber/core/io/ClasspathResourceIterable.java
index 4cedbfdf2f..d13b3ae9a1 100644
--- a/core/src/main/java/cucumber/runtime/io/ClasspathResourceIterable.java
+++ b/core/src/main/java/io/cucumber/core/io/ClasspathResourceIterable.java
@@ -1,6 +1,6 @@
-package cucumber.runtime.io;
+package io.cucumber.core.io;
-import cucumber.runtime.CucumberException;
+import io.cucumber.core.exception.CucumberException;
import java.io.IOException;
import java.net.URI;
@@ -9,9 +9,10 @@
import java.util.Enumeration;
import java.util.Iterator;
-import static io.cucumber.core.model.Classpath.resourceName;
+import static io.cucumber.core.io.Classpath.resourceName;
+
+final class ClasspathResourceIterable implements Iterable {
-class ClasspathResourceIterable implements Iterable {
private final ResourceIteratorFactory resourceIteratorFactory =
new DelegatingResourceIteratorFactory(new ZipThenFileResourceIteratorFactory());
diff --git a/core/src/main/java/cucumber/runtime/io/ClasspathResourceLoader.java b/core/src/main/java/io/cucumber/core/io/ClasspathResourceLoader.java
similarity index 63%
rename from core/src/main/java/cucumber/runtime/io/ClasspathResourceLoader.java
rename to core/src/main/java/io/cucumber/core/io/ClasspathResourceLoader.java
index 6ce22d94f6..69b37ed44a 100644
--- a/core/src/main/java/cucumber/runtime/io/ClasspathResourceLoader.java
+++ b/core/src/main/java/io/cucumber/core/io/ClasspathResourceLoader.java
@@ -1,11 +1,11 @@
-package cucumber.runtime.io;
+package io.cucumber.core.io;
import java.net.URI;
-public class ClasspathResourceLoader implements ResourceLoader {
+final class ClasspathResourceLoader implements ResourceLoader {
private final ClassLoader classLoader;
- public ClasspathResourceLoader(ClassLoader classLoader) {
+ ClasspathResourceLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
diff --git a/core/src/main/java/cucumber/runtime/io/DelegatingResourceIteratorFactory.java b/core/src/main/java/io/cucumber/core/io/DelegatingResourceIteratorFactory.java
similarity index 91%
rename from core/src/main/java/cucumber/runtime/io/DelegatingResourceIteratorFactory.java
rename to core/src/main/java/io/cucumber/core/io/DelegatingResourceIteratorFactory.java
index 17704ac982..44b9fe5da6 100644
--- a/core/src/main/java/cucumber/runtime/io/DelegatingResourceIteratorFactory.java
+++ b/core/src/main/java/io/cucumber/core/io/DelegatingResourceIteratorFactory.java
@@ -1,6 +1,6 @@
-package cucumber.runtime.io;
+package io.cucumber.core.io;
-import cucumber.runtime.CucumberException;
+import io.cucumber.core.exception.CucumberException;
import java.net.URI;
import java.util.Iterator;
@@ -11,7 +11,7 @@
* A {@link ResourceIteratorFactory} implementation which delegates to
* factories found by the ServiceLoader class.
*/
-class DelegatingResourceIteratorFactory implements ResourceIteratorFactory {
+final class DelegatingResourceIteratorFactory implements ResourceIteratorFactory {
private final Iterable delegates = ServiceLoader.load(ResourceIteratorFactory.class);
diff --git a/core/src/main/java/cucumber/runtime/io/FileResource.java b/core/src/main/java/io/cucumber/core/io/FileResource.java
similarity index 90%
rename from core/src/main/java/cucumber/runtime/io/FileResource.java
rename to core/src/main/java/io/cucumber/core/io/FileResource.java
index ea334128da..2c6cb5c065 100644
--- a/core/src/main/java/cucumber/runtime/io/FileResource.java
+++ b/core/src/main/java/io/cucumber/core/io/FileResource.java
@@ -1,4 +1,4 @@
-package cucumber.runtime.io;
+package io.cucumber.core.io;
import java.io.File;
import java.io.FileInputStream;
@@ -7,10 +7,11 @@
import java.net.URI;
import java.net.URISyntaxException;
-import static io.cucumber.core.model.Classpath.CLASSPATH_SCHEME;
-import static cucumber.runtime.io.MultiLoader.FILE_SCHEME;
+import static io.cucumber.core.io.MultiLoader.FILE_SCHEME;
+import static io.cucumber.core.io.Classpath.CLASSPATH_SCHEME;
-class FileResource implements Resource {
+
+final class FileResource implements Resource {
private final File root;
private final File file;
private final boolean classpathFileResource;
diff --git a/core/src/main/java/cucumber/runtime/io/FileResourceIterable.java b/core/src/main/java/io/cucumber/core/io/FileResourceIterable.java
similarity index 82%
rename from core/src/main/java/cucumber/runtime/io/FileResourceIterable.java
rename to core/src/main/java/io/cucumber/core/io/FileResourceIterable.java
index a3deb5027a..e14a5c0140 100644
--- a/core/src/main/java/cucumber/runtime/io/FileResourceIterable.java
+++ b/core/src/main/java/io/cucumber/core/io/FileResourceIterable.java
@@ -1,9 +1,9 @@
-package cucumber.runtime.io;
+package io.cucumber.core.io;
import java.io.File;
import java.util.Iterator;
-class FileResourceIterable implements Iterable {
+final class FileResourceIterable implements Iterable {
private final File root;
private final File file;
private final String suffix;
diff --git a/core/src/main/java/cucumber/runtime/io/FileResourceIterator.java b/core/src/main/java/io/cucumber/core/io/FileResourceIterator.java
similarity index 95%
rename from core/src/main/java/cucumber/runtime/io/FileResourceIterator.java
rename to core/src/main/java/io/cucumber/core/io/FileResourceIterator.java
index 320819343c..5a8462c210 100644
--- a/core/src/main/java/cucumber/runtime/io/FileResourceIterator.java
+++ b/core/src/main/java/io/cucumber/core/io/FileResourceIterator.java
@@ -1,13 +1,13 @@
-package cucumber.runtime.io;
+package io.cucumber.core.io;
import java.io.File;
import java.io.FileFilter;
import java.util.Iterator;
-import static cucumber.runtime.io.Helpers.hasSuffix;
+import static io.cucumber.core.io.Helpers.hasSuffix;
import static java.util.Arrays.asList;
-class FileResourceIterator implements Iterator {
+final class FileResourceIterator implements Iterator {
private final FlatteningIterator flatteningIterator = new FlatteningIterator();
static FileResourceIterator createFileResourceIterator(File root, File file, final String suffix) {
diff --git a/core/src/main/java/cucumber/runtime/io/FileResourceIteratorFactory.java b/core/src/main/java/io/cucumber/core/io/FileResourceIteratorFactory.java
similarity index 90%
rename from core/src/main/java/cucumber/runtime/io/FileResourceIteratorFactory.java
rename to core/src/main/java/io/cucumber/core/io/FileResourceIteratorFactory.java
index 64cc457c07..666f2a777e 100644
--- a/core/src/main/java/cucumber/runtime/io/FileResourceIteratorFactory.java
+++ b/core/src/main/java/io/cucumber/core/io/FileResourceIteratorFactory.java
@@ -1,4 +1,4 @@
-package cucumber.runtime.io;
+package io.cucumber.core.io;
import java.io.File;
import java.net.URI;
@@ -14,7 +14,7 @@
* service implementation for {@link ResourceIteratorFactory} as it could
* easily hide other service implementations.
*/
-class FileResourceIteratorFactory implements ResourceIteratorFactory {
+final class FileResourceIteratorFactory implements ResourceIteratorFactory {
@Override
public boolean isFactoryFor(URI url) {
diff --git a/core/src/main/java/cucumber/runtime/io/FileResourceLoader.java b/core/src/main/java/io/cucumber/core/io/FileResourceLoader.java
similarity index 79%
rename from core/src/main/java/cucumber/runtime/io/FileResourceLoader.java
rename to core/src/main/java/io/cucumber/core/io/FileResourceLoader.java
index d97b5cb8d5..fabc179ede 100644
--- a/core/src/main/java/cucumber/runtime/io/FileResourceLoader.java
+++ b/core/src/main/java/io/cucumber/core/io/FileResourceLoader.java
@@ -1,11 +1,11 @@
-package cucumber.runtime.io;
+package io.cucumber.core.io;
import java.io.File;
import java.net.URI;
-import static cucumber.runtime.io.MultiLoader.FILE_SCHEME;
+import static io.cucumber.core.io.MultiLoader.FILE_SCHEME;
-class FileResourceLoader implements ResourceLoader {
+final class FileResourceLoader implements ResourceLoader {
@Override
public Iterable resources(URI path, String suffix) {
if(!FILE_SCHEME.equals(path.getScheme())){
diff --git a/core/src/main/java/cucumber/runtime/io/FlatteningIterator.java b/core/src/main/java/io/cucumber/core/io/FlatteningIterator.java
similarity index 94%
rename from core/src/main/java/cucumber/runtime/io/FlatteningIterator.java
rename to core/src/main/java/io/cucumber/core/io/FlatteningIterator.java
index b8f3d98d72..5c17814472 100644
--- a/core/src/main/java/cucumber/runtime/io/FlatteningIterator.java
+++ b/core/src/main/java/io/cucumber/core/io/FlatteningIterator.java
@@ -1,11 +1,11 @@
-package cucumber.runtime.io;
+package io.cucumber.core.io;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.NoSuchElementException;
-class FlatteningIterator implements Iterator {
+final class FlatteningIterator implements Iterator {
private final Deque> iterators = new ArrayDeque>();
private T next;
diff --git a/core/src/main/java/cucumber/runtime/io/Helpers.java b/core/src/main/java/io/cucumber/core/io/Helpers.java
similarity index 83%
rename from core/src/main/java/cucumber/runtime/io/Helpers.java
rename to core/src/main/java/io/cucumber/core/io/Helpers.java
index 2e722c3239..a061c9289f 100644
--- a/core/src/main/java/cucumber/runtime/io/Helpers.java
+++ b/core/src/main/java/io/cucumber/core/io/Helpers.java
@@ -1,10 +1,10 @@
-package cucumber.runtime.io;
+package io.cucumber.core.io;
-import cucumber.runtime.CucumberException;
+import io.cucumber.core.exception.CucumberException;
import java.net.URI;
-class Helpers {
+final class Helpers {
private Helpers() {
}
diff --git a/core/src/main/java/cucumber/runtime/io/MultiLoader.java b/core/src/main/java/io/cucumber/core/io/MultiLoader.java
similarity index 83%
rename from core/src/main/java/cucumber/runtime/io/MultiLoader.java
rename to core/src/main/java/io/cucumber/core/io/MultiLoader.java
index 984392fb10..cee8cebf9c 100644
--- a/core/src/main/java/cucumber/runtime/io/MultiLoader.java
+++ b/core/src/main/java/io/cucumber/core/io/MultiLoader.java
@@ -1,10 +1,10 @@
-package cucumber.runtime.io;
+package io.cucumber.core.io;
import java.net.URI;
-import static io.cucumber.core.model.Classpath.CLASSPATH_SCHEME;
+import static io.cucumber.core.io.Classpath.CLASSPATH_SCHEME;
-public class MultiLoader implements ResourceLoader {
+public final class MultiLoader implements ResourceLoader {
static final String FILE_SCHEME = "file";
private final ClasspathResourceLoader classpath;
diff --git a/core/src/main/java/cucumber/runtime/io/Resource.java b/core/src/main/java/io/cucumber/core/io/Resource.java
similarity index 86%
rename from core/src/main/java/cucumber/runtime/io/Resource.java
rename to core/src/main/java/io/cucumber/core/io/Resource.java
index e8b393f36a..129c865e5e 100644
--- a/core/src/main/java/cucumber/runtime/io/Resource.java
+++ b/core/src/main/java/io/cucumber/core/io/Resource.java
@@ -1,4 +1,4 @@
-package cucumber.runtime.io;
+package io.cucumber.core.io;
import java.io.IOException;
import java.io.InputStream;
diff --git a/core/src/main/java/cucumber/runtime/io/ResourceIteratorFactory.java b/core/src/main/java/io/cucumber/core/io/ResourceIteratorFactory.java
similarity index 91%
rename from core/src/main/java/cucumber/runtime/io/ResourceIteratorFactory.java
rename to core/src/main/java/io/cucumber/core/io/ResourceIteratorFactory.java
index 26533f00e2..72a38c6ae1 100644
--- a/core/src/main/java/cucumber/runtime/io/ResourceIteratorFactory.java
+++ b/core/src/main/java/io/cucumber/core/io/ResourceIteratorFactory.java
@@ -1,4 +1,4 @@
-package cucumber.runtime.io;
+package io.cucumber.core.io;
import java.net.URI;
import java.util.Iterator;
@@ -6,7 +6,7 @@
/**
* Factory contract for creating resource iterators.
*/
-public interface ResourceIteratorFactory {
+interface ResourceIteratorFactory {
/**
* Gets a value indicating whether the factory can create iterators for the
diff --git a/core/src/main/java/cucumber/runtime/io/ResourceLoader.java b/core/src/main/java/io/cucumber/core/io/ResourceLoader.java
similarity index 80%
rename from core/src/main/java/cucumber/runtime/io/ResourceLoader.java
rename to core/src/main/java/io/cucumber/core/io/ResourceLoader.java
index 27d7d6081d..0a1ef56da1 100644
--- a/core/src/main/java/cucumber/runtime/io/ResourceLoader.java
+++ b/core/src/main/java/io/cucumber/core/io/ResourceLoader.java
@@ -1,4 +1,4 @@
-package cucumber.runtime.io;
+package io.cucumber.core.io;
import java.net.URI;
diff --git a/core/src/main/java/cucumber/runtime/io/ResourceLoaderClassFinder.java b/core/src/main/java/io/cucumber/core/io/ResourceLoaderClassFinder.java
similarity index 90%
rename from core/src/main/java/cucumber/runtime/io/ResourceLoaderClassFinder.java
rename to core/src/main/java/io/cucumber/core/io/ResourceLoaderClassFinder.java
index 6bd2f57516..37d0eea084 100644
--- a/core/src/main/java/cucumber/runtime/io/ResourceLoaderClassFinder.java
+++ b/core/src/main/java/io/cucumber/core/io/ResourceLoaderClassFinder.java
@@ -1,13 +1,10 @@
-package cucumber.runtime.io;
-
-import cucumber.runtime.ClassFinder;
-import io.cucumber.core.model.Classpath;
+package io.cucumber.core.io;
import java.net.URI;
import java.util.Collection;
import java.util.HashSet;
-public class ResourceLoaderClassFinder implements ClassFinder {
+public final class ResourceLoaderClassFinder implements ClassFinder {
private static final String CLASS_SUFFIX = ".class";
private static final char DOT = '.';
private static final char PACKAGE_PATH_SEPARATOR = '/';
diff --git a/core/src/main/java/cucumber/runtime/io/ZipResource.java b/core/src/main/java/io/cucumber/core/io/ZipResource.java
similarity index 84%
rename from core/src/main/java/cucumber/runtime/io/ZipResource.java
rename to core/src/main/java/io/cucumber/core/io/ZipResource.java
index b9aacbf198..b6563bc8f1 100644
--- a/core/src/main/java/cucumber/runtime/io/ZipResource.java
+++ b/core/src/main/java/io/cucumber/core/io/ZipResource.java
@@ -1,4 +1,4 @@
-package cucumber.runtime.io;
+package io.cucumber.core.io;
import java.io.IOException;
import java.io.InputStream;
@@ -7,9 +7,9 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
-import static io.cucumber.core.model.Classpath.CLASSPATH_SCHEME;
+import static io.cucumber.core.io.Classpath.CLASSPATH_SCHEME;
-class ZipResource implements Resource {
+final class ZipResource implements Resource {
private final ZipFile jarFile;
private final ZipEntry jarEntry;
diff --git a/core/src/main/java/cucumber/runtime/io/ZipResourceIterator.java b/core/src/main/java/io/cucumber/core/io/ZipResourceIterator.java
similarity index 94%
rename from core/src/main/java/cucumber/runtime/io/ZipResourceIterator.java
rename to core/src/main/java/io/cucumber/core/io/ZipResourceIterator.java
index 74beca7789..a60e92e530 100644
--- a/core/src/main/java/cucumber/runtime/io/ZipResourceIterator.java
+++ b/core/src/main/java/io/cucumber/core/io/ZipResourceIterator.java
@@ -1,4 +1,4 @@
-package cucumber.runtime.io;
+package io.cucumber.core.io;
import java.io.File;
import java.io.IOException;
@@ -9,7 +9,7 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
-class ZipResourceIterator implements Iterator {
+final class ZipResourceIterator implements Iterator {
private final String path;
private final String suffix;
private final ZipFile jarFile;
diff --git a/core/src/main/java/cucumber/runtime/io/ZipResourceIteratorFactory.java b/core/src/main/java/io/cucumber/core/io/ZipResourceIteratorFactory.java
similarity index 80%
rename from core/src/main/java/cucumber/runtime/io/ZipResourceIteratorFactory.java
rename to core/src/main/java/io/cucumber/core/io/ZipResourceIteratorFactory.java
index 886d01ed99..cdee083d1c 100644
--- a/core/src/main/java/cucumber/runtime/io/ZipResourceIteratorFactory.java
+++ b/core/src/main/java/io/cucumber/core/io/ZipResourceIteratorFactory.java
@@ -1,16 +1,16 @@
-package cucumber.runtime.io;
+package io.cucumber.core.io;
import java.io.IOException;
import java.net.URI;
import java.util.Iterator;
-import cucumber.runtime.CucumberException;
+import io.cucumber.core.exception.CucumberException;
/**
* Factory which creates {@link ZipResourceIterator}s for URL's with "jar", "zip" and "wsjar"
* protocols.
*/
-class ZipResourceIteratorFactory implements ResourceIteratorFactory {
+final class ZipResourceIteratorFactory implements ResourceIteratorFactory {
@Override
public boolean isFactoryFor(URI url) {
diff --git a/core/src/main/java/cucumber/runtime/io/ZipThenFileResourceIteratorFactory.java b/core/src/main/java/io/cucumber/core/io/ZipThenFileResourceIteratorFactory.java
similarity index 87%
rename from core/src/main/java/cucumber/runtime/io/ZipThenFileResourceIteratorFactory.java
rename to core/src/main/java/io/cucumber/core/io/ZipThenFileResourceIteratorFactory.java
index 9d1404645a..5c323413bb 100644
--- a/core/src/main/java/cucumber/runtime/io/ZipThenFileResourceIteratorFactory.java
+++ b/core/src/main/java/io/cucumber/core/io/ZipThenFileResourceIteratorFactory.java
@@ -1,13 +1,12 @@
-package cucumber.runtime.io;
+package io.cucumber.core.io;
import java.net.URI;
-import java.net.URL;
import java.util.Iterator;
/**
* Resource iterator factory implementation which delegates to zip then file.
*/
-class ZipThenFileResourceIteratorFactory implements ResourceIteratorFactory {
+final class ZipThenFileResourceIteratorFactory implements ResourceIteratorFactory {
private final ResourceIteratorFactory zipResourceIteratorFactory = new ZipResourceIteratorFactory();
private final ResourceIteratorFactory fileResourceIteratorFactory = new FileResourceIteratorFactory();
diff --git a/core/src/main/java/io/cucumber/core/logging/Logger.java b/core/src/main/java/io/cucumber/core/logging/Logger.java
index 6a2600ab2d..a5ca0a0119 100644
--- a/core/src/main/java/io/cucumber/core/logging/Logger.java
+++ b/core/src/main/java/io/cucumber/core/logging/Logger.java
@@ -17,62 +17,91 @@ public interface Logger {
/**
* Log the {@code message} at error level.
+ *
+ * @param message The message to log.
*/
void error(String message);
/**
* Log the {@code message} and {@code throwable} at error level.
+ *
+ * @param message The message to log.
+ * @param throwable The throwable to log.
*/
void error(String message, Throwable throwable);
/**
* Log the {@code message} at warning level.
+ *
+ * @param message The message to log.
*/
void warn(String message);
/**
* Log the {@code message} and {@code throwable} at warning level.
+ *
+ * @param message The message to log.
+ * @param throwable The throwable to log.
*/
void warn(String message, Throwable throwable);
/**
* Log the {@code message} at info level.
+ *
+ * @param message The message to log.
*/
void info(String message);
/**
* Log the {@code message} and {@code throwable} at info level.
+ *
+ * @param message The message to log.
+ * @param throwable The throwable to log.
*/
void info(String message, Throwable throwable);
/**
* Log the {@code message} at config level.
+ *
+ * @param message The message to log.
*/
void config(String message);
/**
* Log the {@code message} and {@code throwable} at config level.
+ *
+ * @param message The message to log.
+ * @param throwable The throwable to log.
*/
void config(String message, Throwable throwable);
/**
* Log the {@code message} at debug level.
+ *
+ * @param message The message to log.
*/
void debug(String message);
/**
* Log {@code message} and {@code throwable} at debug level.
+ *
+ * @param message The message to log.
+ * @param throwable The throwable to log.
*/
void debug(String message, Throwable throwable);
/**
* Log the {@code message} at trace level.
+ *
+ * @param message The message to log.
*/
void trace(String message);
/**
* Log the {@code message} and {@code throwable} at trace level.
+ *
+ * @param message The message to log.
+ * @param throwable The throwable to log.
*/
void trace(String message, Throwable throwable);
-
}
diff --git a/core/src/main/java/io/cucumber/core/options/CommandlineOptionsParser.java b/core/src/main/java/io/cucumber/core/options/CommandlineOptionsParser.java
index 4cb0e950e3..213bfbb39d 100644
--- a/core/src/main/java/io/cucumber/core/options/CommandlineOptionsParser.java
+++ b/core/src/main/java/io/cucumber/core/options/CommandlineOptionsParser.java
@@ -1,8 +1,8 @@
package io.cucumber.core.options;
-import cucumber.runtime.io.MultiLoader;
-import cucumber.runtime.io.ResourceLoader;
-import io.cucumber.core.model.RerunLoader;
+import io.cucumber.core.io.MultiLoader;
+import io.cucumber.core.io.ResourceLoader;
+import io.cucumber.core.feature.RerunLoader;
import java.util.Arrays;
import java.util.List;
diff --git a/core/src/main/java/io/cucumber/core/options/Constants.java b/core/src/main/java/io/cucumber/core/options/Constants.java
new file mode 100644
index 0000000000..7928e99aae
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/options/Constants.java
@@ -0,0 +1,29 @@
+package io.cucumber.core.options;
+
+public final class Constants {
+
+ /**
+ * File name of cucumber properties file: {@value}
+ */
+ public static final String CUCUMBER_PROPERTIES_FILE_NAME = "cucumber.properties";
+
+ /**
+ * Property name used to pass command line options: {@value}
+ *
+ * When available it is recommended to use a property based alternative.
+ *
+ * @see RuntimeOptionsParser
+ */
+ public static final String CUCUMBER_OPTIONS_PROPERTY_NAME = "cucumber.options";
+ /**
+ * Property name used to select a specific object factory implementation: {@value}
+ *
+ * @see io.cucumber.core.backend.ObjectFactoryServiceLoader
+ */
+ public static final String CUCUMBER_OBJECT_FACTORY_PROPERTY_NAME = "cucumber.object-factory";
+
+
+ private Constants() {
+
+ }
+}
diff --git a/core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java b/core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java
index 8286b7b096..a3b61b4e04 100644
--- a/core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java
+++ b/core/src/main/java/io/cucumber/core/options/CucumberOptionsAnnotationParser.java
@@ -1,24 +1,26 @@
package io.cucumber.core.options;
-import cucumber.api.SnippetType;
-import cucumber.runtime.CucumberException;
-import cucumber.runtime.io.MultiLoader;
-import cucumber.runtime.io.ResourceLoader;
-import io.cucumber.core.model.Classpath;
-import io.cucumber.core.model.FeaturePath;
-import io.cucumber.core.model.FeatureWithLines;
-import io.cucumber.core.model.GluePath;
-import io.cucumber.core.model.RerunLoader;
+import io.cucumber.core.snippets.SnippetType;
+import io.cucumber.core.backend.ObjectFactory;
+import io.cucumber.core.exception.CucumberException;
+import io.cucumber.core.io.MultiLoader;
+import io.cucumber.core.io.ResourceLoader;
+import io.cucumber.core.io.Classpath;
+import io.cucumber.core.feature.FeaturePath;
+import io.cucumber.core.feature.FeatureWithLines;
+import io.cucumber.core.feature.GluePath;
+import io.cucumber.core.feature.RerunLoader;
import java.net.URI;
import java.util.regex.Pattern;
+import static java.util.Objects.requireNonNull;
+
public final class CucumberOptionsAnnotationParser {
private final RerunLoader rerunLoader;
private boolean featuresSpecified = false;
private boolean overridingGlueSpecified = false;
private OptionsProvider optionsProvider;
- private CoreCucumberOptionsProvider coreCucumberOptionsProvider = new CoreCucumberOptionsProvider();
public CucumberOptionsAnnotationParser() {
this(new MultiLoader(CucumberOptionsAnnotationParser.class.getClassLoader()));
@@ -37,13 +39,8 @@ public RuntimeOptionsBuilder parse(Class> clazz) {
RuntimeOptionsBuilder args = new RuntimeOptionsBuilder();
for (Class classWithOptions = clazz; hasSuperClass(classWithOptions); classWithOptions = classWithOptions.getSuperclass()) {
- CucumberOptions options = null;
- if (optionsProvider != null) {
- options = optionsProvider.getOptions(classWithOptions);
- }
- if(options == null){
- options = coreCucumberOptionsProvider.getOptions(classWithOptions);
- }
+ CucumberOptions options = requireNonNull(optionsProvider).getOptions(classWithOptions);
+
if (options != null) {
addDryRun(options, args);
addMonochrome(options, args);
@@ -54,7 +51,7 @@ public RuntimeOptionsBuilder parse(Class> clazz) {
addSnippets(options, args);
addGlue(options, args);
addFeatures(options, args);
- addJunitOptions(options, args);
+ addObjectFactory(options, args);
}
}
addDefaultFeaturePathIfNoFeaturePathIsSpecified(args, clazz);
@@ -151,16 +148,15 @@ private void addDefaultGlueIfNoOverridingGlueIsSpecified(RuntimeOptionsBuilder a
}
}
-
private void addStrict(CucumberOptions options, RuntimeOptionsBuilder args) {
if (options.strict()) {
args.setStrict(true);
}
}
- private void addJunitOptions(CucumberOptions options, RuntimeOptionsBuilder args) {
- for (String junitOption : options.junit()) {
- args.addJunitOption(junitOption);
+ private void addObjectFactory(CucumberOptions options, RuntimeOptionsBuilder args) {
+ if (options.objectFactory() != null) {
+ args.setObjectFactoryClass(options.objectFactory());
}
}
@@ -174,7 +170,7 @@ private static String packagePath(Class clazz) {
return Classpath.CLASSPATH_SCHEME_PREFIX + packageName.replace('.', '/');
}
- static String packageName(Class clazz) {
+ private static String packageName(Class clazz) {
String className = clazz.getName();
return className.substring(0, Math.max(0, className.lastIndexOf('.')));
}
@@ -215,81 +211,7 @@ public interface CucumberOptions {
String[] name();
SnippetType snippets();
-
- String[] junit();
- }
-
- private static class CoreCucumberOptions implements CucumberOptions {
- private final cucumber.api.CucumberOptions annotation;
-
- CoreCucumberOptions(cucumber.api.CucumberOptions annotation) {
- this.annotation = annotation;
- }
-
- @Override
- public boolean dryRun() {
- return annotation.dryRun();
- }
-
- @Override
- public boolean strict() {
- return annotation.strict();
- }
-
- @Override
- public String[] features() {
- return annotation.features();
- }
-
- @Override
- public String[] glue() {
- return annotation.glue();
- }
-
- @Override
- public String[] extraGlue() {
- return annotation.extraGlue();
- }
-
- @Override
- public String[] tags() {
- return annotation.tags();
- }
-
- @Override
- public String[] plugin() {
- return annotation.plugin();
- }
-
- @Override
- public boolean monochrome() {
- return annotation.monochrome();
- }
-
- @Override
- public String[] name() {
- return annotation.name();
- }
-
- @Override
- public SnippetType snippets() {
- return annotation.snippets();
- }
-
- @Override
- public String[] junit() {
- return annotation.junit();
- }
- }
-
- private static class CoreCucumberOptionsProvider implements OptionsProvider {
- @Override
- public CucumberOptions getOptions(Class> clazz) {
- final cucumber.api.CucumberOptions annotation = clazz.getAnnotation(cucumber.api.CucumberOptions.class);
- if (annotation == null) {
- return null;
- }
- return new CoreCucumberOptions(annotation);
- }
+
+ Class extends ObjectFactory> objectFactory();
}
}
diff --git a/core/src/main/java/io/cucumber/core/options/CucumberProperties.java b/core/src/main/java/io/cucumber/core/options/CucumberProperties.java
new file mode 100644
index 0000000000..aec262e7f7
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/options/CucumberProperties.java
@@ -0,0 +1,124 @@
+package io.cucumber.core.options;
+
+import io.cucumber.core.logging.Logger;
+import io.cucumber.core.logging.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.*;
+
+import static io.cucumber.core.options.Constants.CUCUMBER_PROPERTIES_FILE_NAME;
+
+public final class CucumberProperties {
+
+ private static final Logger log = LoggerFactory.getLogger(CucumberProperties.class);
+
+ private CucumberProperties() {
+
+ }
+
+ public static Map create() {
+ CucumberPropertiesMap fromBundle = new CucumberPropertiesMap(fromPropertiesFile());
+ CucumberPropertiesMap fromEnvironmentProperties = new CucumberPropertiesMap(fromBundle, fromEnvironment());
+ return new CucumberPropertiesMap(fromEnvironmentProperties, fromSystemProperties());
+ }
+
+ public static Map fromPropertiesFile() {
+ InputStream resourceAsStream = CucumberProperties.class.getResourceAsStream("/" + CUCUMBER_PROPERTIES_FILE_NAME);
+ if (resourceAsStream == null) {
+ log.debug(CUCUMBER_PROPERTIES_FILE_NAME + " file did not exist");
+ return Collections.emptyMap();
+ }
+
+ try {
+ Properties properties = new Properties();
+ properties.load(resourceAsStream);
+ return CucumberPropertiesMap.create(properties);
+ } catch (IOException e) {
+ log.error(CUCUMBER_PROPERTIES_FILE_NAME + " could not be loaded", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static Map fromSystemProperties() {
+ Properties p = System.getProperties();
+ return CucumberPropertiesMap.create(p);
+ }
+
+ public static Map fromEnvironment() {
+ Map p = System.getenv();
+ CucumberPropertiesMap properties = new CucumberPropertiesMap();
+ properties.putAll(p);
+ return properties;
+ }
+
+ static class CucumberPropertiesMap extends HashMap {
+
+ private final CucumberPropertiesMap parent;
+
+ CucumberPropertiesMap() {
+ this(null);
+ }
+
+ CucumberPropertiesMap(CucumberPropertiesMap parent) {
+ this(parent, Collections.emptyMap());
+ }
+
+ CucumberPropertiesMap(Map properties) {
+ this(null, properties);
+ }
+
+ CucumberPropertiesMap(CucumberPropertiesMap parent, Map properties) {
+ super(properties);
+ this.parent = parent;
+ }
+
+ private static CucumberPropertiesMap create(Properties p) {
+ CucumberPropertiesMap properties = new CucumberPropertiesMap();
+ for (String key : p.stringPropertyNames()) {
+ properties.put(key, p.getProperty(key));
+ }
+ return properties;
+ }
+
+ @Override
+ public String get(Object key) {
+ String exactMatch = super.get(key);
+ if (exactMatch != null) {
+ return exactMatch;
+ }
+
+ if (!(key instanceof String)) {
+ 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(".", "_")
+ .replace("-", "_")
+ .toUpperCase(Locale.ENGLISH);
+ String upperCaseMatch = super.get(uppercase);
+ if (upperCaseMatch != null) {
+ return upperCaseMatch;
+ }
+
+ String lowercase = keyString
+ .replace(".", "_")
+ .replace("-", "_")
+ .toLowerCase(Locale.ENGLISH);
+ String lowerValue = super.get(lowercase);
+ if (lowerValue != null)
+ return lowerValue;
+
+ if (parent == null) {
+ return null;
+ }
+ return parent.get(key);
+ }
+
+ }
+}
diff --git a/core/src/main/java/io/cucumber/core/options/CucumberPropertiesParser.java b/core/src/main/java/io/cucumber/core/options/CucumberPropertiesParser.java
new file mode 100644
index 0000000000..97a4d7da9b
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/options/CucumberPropertiesParser.java
@@ -0,0 +1,62 @@
+package io.cucumber.core.options;
+
+import io.cucumber.core.backend.ObjectFactory;
+import io.cucumber.core.exception.CucumberException;
+import io.cucumber.core.io.MultiLoader;
+import io.cucumber.core.io.ResourceLoader;
+import io.cucumber.core.feature.RerunLoader;
+
+import java.util.List;
+import java.util.Map;
+
+import static io.cucumber.core.options.Constants.CUCUMBER_OBJECT_FACTORY_PROPERTY_NAME;
+import static io.cucumber.core.options.Constants.CUCUMBER_OPTIONS_PROPERTY_NAME;
+
+public final class CucumberPropertiesParser {
+
+ private final ResourceLoader resourceLoader;
+
+ public CucumberPropertiesParser(ResourceLoader resourceLoader) {
+ this.resourceLoader = resourceLoader;
+ }
+
+ public CucumberPropertiesParser() {
+ this(new MultiLoader(CucumberPropertiesParser.class.getClassLoader()));
+ }
+
+ public RuntimeOptionsBuilder parse(Map properties) {
+ final RuntimeOptionsBuilder builder;
+ String cucumberOptions = properties.get(CUCUMBER_OPTIONS_PROPERTY_NAME);
+ if (cucumberOptions != null) {
+ RerunLoader rerunLoader = new RerunLoader(resourceLoader);
+ RuntimeOptionsParser parser = new RuntimeOptionsParser(rerunLoader);
+ List args = ShellWords.parse(cucumberOptions);
+ builder = parser.parse(args);
+ } else {
+ builder = new RuntimeOptionsBuilder();
+ }
+
+ String cucumberObjectFactory = properties.get(CUCUMBER_OBJECT_FACTORY_PROPERTY_NAME);
+ if (cucumberObjectFactory != null) {
+ Class extends ObjectFactory> objectFactoryClass = parseObjectFactory(cucumberObjectFactory);
+ builder.setObjectFactoryClass(objectFactoryClass);
+ }
+
+ return builder;
+ }
+
+ @SuppressWarnings("unchecked")
+ static Class extends ObjectFactory> parseObjectFactory(String cucumberObjectFactory) {
+ Class> objectFactoryClass;
+ try {
+ objectFactoryClass = Class.forName(cucumberObjectFactory);
+ } catch (ClassNotFoundException e) {
+ throw new CucumberException("Could not load object factory class for " + cucumberObjectFactory, e);
+ }
+ if (!ObjectFactory.class.isAssignableFrom(objectFactoryClass)) {
+ throw new CucumberException("Object factory class " + objectFactoryClass + " was not a subclass of " + ObjectFactory.class);
+ }
+ return (Class extends ObjectFactory>) objectFactoryClass;
+ }
+
+}
diff --git a/core/src/main/java/io/cucumber/core/options/EnvironmentOptionsParser.java b/core/src/main/java/io/cucumber/core/options/EnvironmentOptionsParser.java
deleted file mode 100644
index 7eb36ac487..0000000000
--- a/core/src/main/java/io/cucumber/core/options/EnvironmentOptionsParser.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package io.cucumber.core.options;
-
-import cucumber.runtime.Env;
-import cucumber.runtime.Shellwords;
-import cucumber.runtime.io.MultiLoader;
-import cucumber.runtime.io.ResourceLoader;
-import io.cucumber.core.model.RerunLoader;
-
-import java.util.Collections;
-import java.util.List;
-
-public class EnvironmentOptionsParser {
-
- private final ResourceLoader resourceLoader;
-
-
- public EnvironmentOptionsParser(ResourceLoader resourceLoader) {
- this.resourceLoader = resourceLoader;
- }
-
- public EnvironmentOptionsParser() {
- this(new MultiLoader(EnvironmentOptionsParser.class.getClassLoader()));
- }
-
- public RuntimeOptionsBuilder parse(Env env){
- RerunLoader rerunLoader = new RerunLoader(resourceLoader);
- RuntimeOptionsParser parser = new RuntimeOptionsParser(rerunLoader);
-
- String cucumberOptionsFromEnv = env.get("cucumber.options");
- List shellWords = Collections.emptyList();
- if (cucumberOptionsFromEnv != null) {
- shellWords = Shellwords.parse(cucumberOptionsFromEnv);
- }
- return parser.parse(shellWords);
- }
-
-}
diff --git a/core/src/main/java/io/cucumber/core/options/PluginOption.java b/core/src/main/java/io/cucumber/core/options/PluginOption.java
new file mode 100644
index 0000000000..6016f167f8
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/options/PluginOption.java
@@ -0,0 +1,128 @@
+package io.cucumber.core.options;
+
+import io.cucumber.core.plugin.Plugin;
+import io.cucumber.core.plugin.SummaryPrinter;
+import io.cucumber.core.plugin.ConcurrentEventListener;
+import io.cucumber.core.plugin.EventListener;
+import io.cucumber.core.exception.CucumberException;
+import io.cucumber.core.logging.Logger;
+import io.cucumber.core.logging.LoggerFactory;
+import io.cucumber.core.plugin.DefaultSummaryPrinter;
+import io.cucumber.core.plugin.HTMLFormatter;
+import io.cucumber.core.plugin.JSONFormatter;
+import io.cucumber.core.plugin.JUnitFormatter;
+import io.cucumber.core.plugin.NullSummaryPrinter;
+import io.cucumber.core.plugin.Options;
+import io.cucumber.core.plugin.PrettyFormatter;
+import io.cucumber.core.plugin.ProgressFormatter;
+import io.cucumber.core.plugin.RerunFormatter;
+import io.cucumber.core.plugin.TestNGFormatter;
+import io.cucumber.core.plugin.TimelineFormatter;
+import io.cucumber.core.plugin.UnusedStepsSummaryPrinter;
+import io.cucumber.core.plugin.UsageFormatter;
+
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class PluginOption implements Options.Plugin {
+
+ private static final Logger log = LoggerFactory.getLogger(PluginOption.class);
+
+ private static final Pattern PLUGIN_WITH_ARGUMENT_PATTERN = Pattern.compile("([^:]+):(.*)");
+ private static final HashMap> PLUGIN_CLASSES = new HashMap>() {{
+ put("junit", JUnitFormatter.class);
+ put("testng", TestNGFormatter.class);
+ put("html", HTMLFormatter.class);
+ put("pretty", PrettyFormatter.class);
+ put("progress", ProgressFormatter.class);
+ put("json", JSONFormatter.class);
+ put("usage", UsageFormatter.class);
+ put("rerun", RerunFormatter.class);
+ put("summary", DefaultSummaryPrinter.class);
+ put("default_summary", DefaultSummaryPrinter.class);
+ put("null_summary", NullSummaryPrinter.class);
+ put("unused", UnusedStepsSummaryPrinter.class);
+ put("timeline", TimelineFormatter.class);
+ }};
+
+ // Refuse plugins known to implement the old API
+ private static final HashMap> OLD_INTELLIJ_IDEA_PLUGIN_CLASSES = new HashMap>() {{
+ put("org.jetbrains.plugins.cucumber.java.run.CucumberJvmSMFormatter", PrettyFormatter.class);
+ put("org.jetbrains.plugins.cucumber.java.run.CucumberJvm2SMFormatter", PrettyFormatter.class);
+ put("org.jetbrains.plugins.cucumber.java.run.CucumberJvm3SMFormatter", PrettyFormatter.class);
+ }};
+
+ private final String pluginString;
+ private final Class extends Plugin> pluginClass;
+ private final String argument;
+
+ private PluginOption(String pluginString, Class extends Plugin> pluginClass, String argument) {
+ this.pluginString = pluginString;
+ this.pluginClass = pluginClass;
+ this.argument = argument;
+ }
+
+ @Override
+ public Class extends Plugin> pluginClass() {
+ return pluginClass;
+ }
+
+ @Override
+ public String argument() {
+ return argument;
+ }
+
+ @Override
+ public String pluginString() {
+ return pluginString;
+ }
+
+ boolean isFormatter() {
+ return EventListener.class.isAssignableFrom(pluginClass) || ConcurrentEventListener.class.isAssignableFrom(pluginClass);
+ }
+
+ boolean isSummaryPrinter() {
+ return SummaryPrinter.class.isAssignableFrom(pluginClass);
+ }
+
+ static PluginOption parse(String pluginArgumentPattern) {
+ Matcher pluginWithFile = PLUGIN_WITH_ARGUMENT_PATTERN.matcher(pluginArgumentPattern);
+ if (!pluginWithFile.matches()) {
+ return new PluginOption(pluginArgumentPattern, parsePluginName(pluginArgumentPattern), null);
+ }
+
+ Class extends Plugin> pluginClass = parsePluginName(pluginWithFile.group(1));
+ return new PluginOption(pluginArgumentPattern, pluginClass, pluginWithFile.group(2));
+ }
+
+ private static Class extends Plugin> parsePluginName(String pluginName) {
+ Class extends Plugin> oldApiPlugin = OLD_INTELLIJ_IDEA_PLUGIN_CLASSES.get(pluginName);
+ if (oldApiPlugin != null) {
+ log.warn("Incompatible IntelliJ IDEA Plugin detected. Falling back to pretty formatter");
+ return oldApiPlugin;
+ }
+
+ Class extends Plugin> pluginClass = PLUGIN_CLASSES.get(pluginName);
+ if (pluginClass == null) {
+ pluginClass = loadClass(pluginName);
+ }
+ return pluginClass;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Class extends Plugin> loadClass(String className) {
+ try {
+ Class> aClass = Thread.currentThread().getContextClassLoader().loadClass(className);
+
+ if (Plugin.class.isAssignableFrom(aClass)) {
+ return (Class extends Plugin>) aClass;
+ }
+ throw new CucumberException("Couldn't load plugin class: " + className + ". It does not implement " + Plugin.class.getName());
+ } catch (ClassNotFoundException e) {
+ throw new CucumberException("Couldn't load plugin class: " + className, e);
+ }
+ }
+
+
+}
diff --git a/core/src/main/java/io/cucumber/core/options/PluginOptions.java b/core/src/main/java/io/cucumber/core/options/PluginOptions.java
deleted file mode 100644
index 680158d5a0..0000000000
--- a/core/src/main/java/io/cucumber/core/options/PluginOptions.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package io.cucumber.core.options;
-
-import java.util.List;
-
-public interface PluginOptions {
- List getPluginNames();
-
- boolean isStrict();
-
- boolean isMonochrome();
-
-}
diff --git a/core/src/main/java/io/cucumber/core/options/RunnerOptions.java b/core/src/main/java/io/cucumber/core/options/RunnerOptions.java
deleted file mode 100644
index 6278874be3..0000000000
--- a/core/src/main/java/io/cucumber/core/options/RunnerOptions.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package io.cucumber.core.options;
-
-import cucumber.api.SnippetType;
-
-import java.net.URI;
-import java.util.List;
-
-public interface RunnerOptions {
- List getGlue();
-
- boolean isDryRun();
-
- SnippetType getSnippetType();
-}
diff --git a/core/src/main/java/io/cucumber/core/options/RuntimeOptions.java b/core/src/main/java/io/cucumber/core/options/RuntimeOptions.java
index 7862525eb2..04d131cb25 100644
--- a/core/src/main/java/io/cucumber/core/options/RuntimeOptions.java
+++ b/core/src/main/java/io/cucumber/core/options/RuntimeOptions.java
@@ -1,8 +1,9 @@
package io.cucumber.core.options;
-import cucumber.api.SnippetType;
-import cucumber.runtime.order.PickleOrder;
-import cucumber.runtime.order.StandardPickleOrders;
+import io.cucumber.core.backend.ObjectFactory;
+import io.cucumber.core.order.PickleOrder;
+import io.cucumber.core.order.StandardPickleOrders;
+import io.cucumber.core.snippets.SnippetType;
import java.net.URI;
import java.util.ArrayList;
@@ -17,15 +18,19 @@
import static java.util.Collections.unmodifiableList;
import static java.util.Collections.unmodifiableMap;
-public final class RuntimeOptions implements FeatureOptions, FilterOptions, PluginOptions, RunnerOptions {
+public final class RuntimeOptions implements
+ io.cucumber.core.feature.Options,
+ io.cucumber.core.runner.Options,
+ io.cucumber.core.plugin.Options,
+ io.cucumber.core.filter.Options,
+ io.cucumber.core.backend.Options {
private final List glue = new ArrayList<>();
- private final List tagFilters = new ArrayList<>();
+ private final List tagExpressions = new ArrayList<>();
private final List nameFilters = new ArrayList<>();
private final Map> lineFilters = new HashMap<>();
private final SortedSet featurePaths = new TreeSet<>();
- private final List junitOptions = new ArrayList<>();
private boolean dryRun;
private boolean strict = false;
private boolean monochrome = false;
@@ -35,9 +40,9 @@ public final class RuntimeOptions implements FeatureOptions, FilterOptions, Plug
private PickleOrder pickleOrder = StandardPickleOrders.lexicalUriOrder();
private int count = 0;
- private final List pluginFormatterNames = new ArrayList<>();
- private final List pluginStepDefinitionReporterNames = new ArrayList<>();
- private final List pluginSummaryPrinterNames = new ArrayList<>();
+ private final List formatters = new ArrayList<>();
+ private final List summaryPrinters = new ArrayList<>();
+ private Class extends ObjectFactory> objectFactoryClass;
private RuntimeOptions() {
@@ -51,16 +56,12 @@ public int getCount() {
return count;
}
- List getPluginFormatterNames() {
- return pluginFormatterNames;
+ List getFormatters() {
+ return formatters;
}
- List getPluginStepDefinitionReporterNames() {
- return pluginStepDefinitionReporterNames;
- }
-
- List getPluginSummaryPrinterNames() {
- return pluginSummaryPrinterNames;
+ List getSummaryPrinter() {
+ return summaryPrinters;
}
public boolean isMultiThreaded() {
@@ -68,12 +69,11 @@ public boolean isMultiThreaded() {
}
@Override
- public List getPluginNames() {
- List pluginNames = new ArrayList<>();
- pluginNames.addAll(getPluginFormatterNames());
- pluginNames.addAll(getPluginStepDefinitionReporterNames());
- pluginNames.addAll(getPluginSummaryPrinterNames());
- return pluginNames;
+ public List plugins() {
+ List plugins = new ArrayList<>();
+ plugins.addAll(formatters);
+ plugins.addAll(summaryPrinters);
+ return plugins;
}
@Override
@@ -101,13 +101,13 @@ public List getFeaturePaths() {
}
@Override
- public List getNameFilters() {
- return unmodifiableList(nameFilters);
+ public List getTagExpressions() {
+ return unmodifiableList(tagExpressions);
}
@Override
- public List getTagFilters() {
- return unmodifiableList(tagFilters);
+ public List getNameFilters() {
+ return unmodifiableList(nameFilters);
}
void setCount(int count) {
@@ -124,11 +124,6 @@ void setGlue(List parsedGlue) {
glue.addAll(parsedGlue);
}
- void setJunitOptions(List junitOptions) {
- this.junitOptions.clear();
- this.junitOptions.addAll(junitOptions);
- }
-
void setLineFilters(Map> lineFilters) {
this.lineFilters.clear();
for (URI path : lineFilters.keySet()) {
@@ -145,9 +140,9 @@ void setPickleOrder(PickleOrder pickleOrder) {
this.pickleOrder = pickleOrder;
}
- void setTagFilters(List tagFilters) {
- this.tagFilters.clear();
- this.tagFilters.addAll(tagFilters);
+ void setTagExpressions(List tagExpressions) {
+ this.tagExpressions.clear();
+ this.tagExpressions.addAll(tagExpressions);
}
@Override
@@ -170,8 +165,9 @@ public SnippetType getSnippetType() {
return snippetType;
}
- public List getJunitOptions() {
- return unmodifiableList(junitOptions);
+ @Override
+ public Class extends ObjectFactory> getObjectFactoryClass() {
+ return objectFactoryClass;
}
public int getThreads() {
@@ -205,4 +201,8 @@ void setThreads(int threads) {
void setWip(boolean wip) {
this.wip = wip;
}
+
+ void setObjectFactoryClass(Class extends ObjectFactory> objectFactoryClass) {
+ this.objectFactoryClass = objectFactoryClass;
+ }
}
diff --git a/core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java b/core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java
index a9b7172a7e..d5f152b4ca 100644
--- a/core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java
+++ b/core/src/main/java/io/cucumber/core/options/RuntimeOptionsBuilder.java
@@ -1,10 +1,11 @@
package io.cucumber.core.options;
-import cucumber.api.SnippetType;
-import cucumber.runtime.CucumberException;
-import cucumber.runtime.formatter.PluginFactory;
-import cucumber.runtime.order.PickleOrder;
-import io.cucumber.core.model.FeatureWithLines;
+import io.cucumber.core.backend.ObjectFactory;
+import io.cucumber.core.snippets.SnippetType;
+import io.cucumber.core.exception.CucumberException;
+import io.cucumber.core.feature.FeatureWithLines;
+import io.cucumber.core.order.PickleOrder;
+import io.cucumber.core.plugin.Options;
import java.net.URI;
import java.util.ArrayList;
@@ -23,7 +24,6 @@ public final class RuntimeOptionsBuilder {
private List parsedFeaturePaths = new ArrayList<>();
private List parsedGlue = new ArrayList<>();
private ParsedPluginData parsedPluginData = new ParsedPluginData();
- private List parsedJunitOptions = new ArrayList<>();
private boolean parsedIsRerun = false;
private Integer parsedThreads = null;
private Boolean parsedDryRun = null;
@@ -33,6 +33,7 @@ public final class RuntimeOptionsBuilder {
private Boolean parsedWip = null;
private PickleOrder parsedPickleOrder = null;
private Integer parsedCount = null;
+ private Class extends ObjectFactory> parsedObjectFactoryClass = null;
public RuntimeOptionsBuilder addFeature(FeatureWithLines featureWithLines) {
parsedFeaturePaths.add(featureWithLines.uri());
@@ -45,11 +46,6 @@ public RuntimeOptionsBuilder addGlue(URI glue) {
return this;
}
- public RuntimeOptionsBuilder addJunitOption(String junitOption) {
- this.parsedJunitOptions.add(junitOption);
- return this;
- }
-
private RuntimeOptionsBuilder addLineFilters(FeatureWithLines featureWithLines) {
URI key = featureWithLines.uri();
Set lines = featureWithLines.lines();
@@ -121,7 +117,7 @@ public RuntimeOptions build(RuntimeOptions runtimeOptions) {
runtimeOptions.setLineFilters(Collections.>emptyMap());
}
if (!this.parsedTagFilters.isEmpty() || !this.parsedNameFilters.isEmpty() || !this.parsedLineFilters.isEmpty()) {
- runtimeOptions.setTagFilters(this.parsedTagFilters);
+ runtimeOptions.setTagExpressions(this.parsedTagFilters);
runtimeOptions.setNameFilters(this.parsedNameFilters);
runtimeOptions.setLineFilters(this.parsedLineFilters);
}
@@ -132,13 +128,13 @@ public RuntimeOptions build(RuntimeOptions runtimeOptions) {
if (!this.parsedGlue.isEmpty()) {
runtimeOptions.setGlue(this.parsedGlue);
}
- if (!this.parsedJunitOptions.isEmpty()) {
- runtimeOptions.setJunitOptions(this.parsedJunitOptions);
- }
- this.parsedPluginData.updatePluginFormatterNames(runtimeOptions.getPluginFormatterNames());
- this.parsedPluginData.updatePluginStepDefinitionReporterNames(runtimeOptions.getPluginStepDefinitionReporterNames());
- this.parsedPluginData.updatePluginSummaryPrinterNames(runtimeOptions.getPluginSummaryPrinterNames());
+ this.parsedPluginData.updateFormatters(runtimeOptions.getFormatters());
+ this.parsedPluginData.updateSummaryPrinters(runtimeOptions.getSummaryPrinter());
+
+ if (parsedObjectFactoryClass != null) {
+ runtimeOptions.setObjectFactoryClass(parsedObjectFactoryClass);
+ }
return runtimeOptions;
}
@@ -209,66 +205,63 @@ public RuntimeOptionsBuilder addDefaultFormatterIfNotPresent() {
return this;
}
- private static class ParsedPluginData {
- ParsedOptionNames formatterNames = new ParsedOptionNames();
- ParsedOptionNames stepDefinitionReporterNames = new ParsedOptionNames();
- ParsedOptionNames summaryPrinterNames = new ParsedOptionNames();
+ public void setObjectFactoryClass(Class extends ObjectFactory> objectFactoryClass) {
+ this.parsedObjectFactoryClass = objectFactoryClass;
+ }
+
+ static final class ParsedPluginData {
+ private ParsedPlugins formatters = new ParsedPlugins();
+ private ParsedPlugins summaryPrinters = new ParsedPlugins();
void addPluginName(String name, boolean isAddPlugin) {
- if (PluginFactory.isStepDefinitionReporterName(name)) {
- stepDefinitionReporterNames.addName(name, isAddPlugin);
- } else if (PluginFactory.isSummaryPrinterName(name)) {
- summaryPrinterNames.addName(name, isAddPlugin);
- } else if (PluginFactory.isFormatterName(name)) {
- formatterNames.addName(name, isAddPlugin);
+ PluginOption pluginOption = PluginOption.parse(name);
+ if (pluginOption.isSummaryPrinter()) {
+ summaryPrinters.addName(pluginOption, isAddPlugin);
+ } else if (pluginOption.isFormatter()) {
+ formatters.addName(pluginOption, isAddPlugin);
} else {
throw new CucumberException("Unrecognized plugin: " + name);
}
}
- void updatePluginFormatterNames(List pluginFormatterNames) {
- formatterNames.updateNameList(pluginFormatterNames);
- }
-
- void updatePluginStepDefinitionReporterNames(List pluginStepDefinitionReporterNames) {
- stepDefinitionReporterNames.updateNameList(pluginStepDefinitionReporterNames);
- }
-
- void updatePluginSummaryPrinterNames(List pluginSummaryPrinterNames) {
- summaryPrinterNames.updateNameList(pluginSummaryPrinterNames);
- }
-
-
void addDefaultSummaryPrinterIfNotPresent() {
- if (summaryPrinterNames.names.isEmpty()) {
- summaryPrinterNames.addName("default_summary", false);
+ if (summaryPrinters.names.isEmpty()) {
+ addPluginName("summary", false);
}
}
void addDefaultFormatterIfNotPresent() {
- if (formatterNames.names.isEmpty()) {
- formatterNames.addName("progress", false);
+ if (formatters.names.isEmpty()) {
+ addPluginName("progress", false);
}
}
- }
- private static class ParsedOptionNames {
- private List names = new ArrayList<>();
- private boolean clobber = false;
+ void updateFormatters(List formatter) {
+ this.formatters.updateNameList(formatter);
+ }
- void addName(String name, boolean isAddOption) {
- names.add(name);
- if (!isAddOption) {
- clobber = true;
- }
+ void updateSummaryPrinters(List pluginSummaryPrinterNames) {
+ summaryPrinters.updateNameList(pluginSummaryPrinterNames);
}
- void updateNameList(List nameList) {
- if (!names.isEmpty()) {
- if (clobber) {
- nameList.clear();
+ private static class ParsedPlugins {
+ private List names = new ArrayList<>();
+ private boolean clobber = false;
+
+ void addName(Options.Plugin name, boolean isAddOption) {
+ names.add(name);
+ if (!isAddOption) {
+ clobber = true;
+ }
+ }
+
+ void updateNameList(List nameList) {
+ if (!names.isEmpty()) {
+ if (clobber) {
+ nameList.clear();
+ }
+ nameList.addAll(names);
}
- nameList.addAll(names);
}
}
}
diff --git a/core/src/main/java/io/cucumber/core/options/RuntimeOptionsParser.java b/core/src/main/java/io/cucumber/core/options/RuntimeOptionsParser.java
index 703c90c88f..dba3e8b63d 100644
--- a/core/src/main/java/io/cucumber/core/options/RuntimeOptionsParser.java
+++ b/core/src/main/java/io/cucumber/core/options/RuntimeOptionsParser.java
@@ -1,24 +1,23 @@
package io.cucumber.core.options;
-import cucumber.api.SnippetType;
-import cucumber.runtime.CucumberException;
-import cucumber.runtime.order.PickleOrder;
-import cucumber.runtime.order.StandardPickleOrders;
-import cucumber.util.FixJava;
-import cucumber.util.Mapper;
import gherkin.GherkinDialect;
import gherkin.GherkinDialectProvider;
import gherkin.IGherkinDialectProvider;
+import io.cucumber.core.exception.CucumberException;
+import io.cucumber.core.feature.FeaturePath;
+import io.cucumber.core.feature.FeatureWithLines;
+import io.cucumber.core.feature.GluePath;
+import io.cucumber.core.feature.RerunLoader;
import io.cucumber.core.logging.Logger;
import io.cucumber.core.logging.LoggerFactory;
-import io.cucumber.core.model.FeaturePath;
-import io.cucumber.core.model.FeatureWithLines;
-import io.cucumber.core.model.GluePath;
-import io.cucumber.core.model.RerunLoader;
+import io.cucumber.core.order.PickleOrder;
+import io.cucumber.core.order.StandardPickleOrders;
+import io.cucumber.core.snippets.SnippetType;
import io.cucumber.datatable.DataTable;
+import java.io.BufferedReader;
+import java.io.InputStream;
import java.io.InputStreamReader;
-import java.io.Reader;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
@@ -26,29 +25,18 @@
import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
-import static cucumber.util.FixJava.join;
-import static cucumber.util.FixJava.map;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.asList;
+import static java.util.stream.Collectors.joining;
final class RuntimeOptionsParser {
private static final Logger log = LoggerFactory.getLogger(RuntimeOptionsParser.class);
- static final String VERSION = ResourceBundle.getBundle("cucumber.version").getString("cucumber-jvm.version");
+ static final String VERSION = ResourceBundle.getBundle("io.cucumber.core.version").getString("cucumber-jvm.version");
private static final Pattern RANDOM_AND_SEED_PATTERN = Pattern.compile("random(?::(\\d+))?");
- private static final Mapper QUOTE_MAPPER = new Mapper() {
- @Override
- public String map(String o) {
- return '"' + o + '"';
- }
- };
- private static final Mapper CODE_KEYWORD_MAPPER = new Mapper() {
- @Override
- public String map(String keyword) {
- return keyword.replaceAll("[\\s',!]", "");
- }
- };
// IMPORTANT! Make sure USAGE.txt is always uptodate if this class changes.
private static final String USAGE_RESOURCE = "/io/cucumber/core/options/USAGE.txt";
static String usageText;
@@ -96,7 +84,7 @@ RuntimeOptionsBuilder parse(List args) {
parsedOptions.setMonochrome(!arg.startsWith("--no-"));
} else if (arg.equals("--snippets")) {
String nextArg = args.remove(0);
- if("underscore".equals(nextArg)){
+ if ("underscore".equals(nextArg)) {
parsedOptions.setSnippetType(SnippetType.UNDERSCORE);
} else if ("camelcase".equals(nextArg)) {
parsedOptions.setSnippetType(SnippetType.CAMELCASE);
@@ -107,10 +95,6 @@ RuntimeOptionsBuilder parse(List args) {
String nextArg = args.remove(0);
Pattern pattern = Pattern.compile(nextArg);
parsedOptions.addNameFilter(pattern);
- } else if (arg.startsWith("--junit,")) {
- for (String parsedOption : arg.substring("--junit,".length()).split(",")) {
- parsedOptions.addJunitOption(parsedOption);
- }
} else if (arg.equals("--wip") || arg.equals("-w")) {
parsedOptions.setWip(true);
} else if (arg.equals("--order")) {
@@ -121,6 +105,9 @@ RuntimeOptionsBuilder parse(List args) {
throw new CucumberException("--count must be > 0");
}
parsedOptions.setCount(count);
+ } else if (arg.equals("--object-factory")) {
+ String objectFactoryClassName = args.remove(0);
+ parsedOptions.setObjectFactoryClass(CucumberPropertiesParser.parseObjectFactory(objectFactoryClassName));
} else if (arg.startsWith("-")) {
printUsage();
throw new CucumberException("Unknown option: " + arg);
@@ -167,9 +154,9 @@ private static void printUsage() {
static void loadUsageTextIfNeeded() {
if (usageText == null) {
- try {
- Reader reader = new InputStreamReader(FixJava.class.getResourceAsStream(USAGE_RESOURCE), "UTF-8");
- usageText = FixJava.readReader(reader);
+ InputStream usageResourceStream = RuntimeOptionsParser.class.getResourceAsStream(USAGE_RESOURCE);
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(usageResourceStream, UTF_8))){
+ usageText = br.lines().collect(joining(System.lineSeparator()));
} catch (Exception e) {
usageText = "Could not load usage text: " + e.toString();
}
@@ -220,13 +207,16 @@ private static int printKeywordsFor(GherkinDialect dialect) {
private static void addCodeKeywordRow(List> table, String key, List keywords) {
List codeKeywordList = new ArrayList<>(keywords);
codeKeywordList.remove("* ");
- addKeywordRow(table, key + " (code)", map(codeKeywordList, CODE_KEYWORD_MAPPER));
+
+ List codeWords = codeKeywordList.stream()
+ .map(keyword -> keyword.replaceAll("[\\s',!]", ""))
+ .collect(Collectors.toList());
+
+ addKeywordRow(table, key + " (code)", codeWords);
}
private static void addKeywordRow(List> table, String key, List keywords) {
- List cells = asList(key, join(map(keywords, QUOTE_MAPPER), ", "));
- table.add(cells);
+ table.add(asList(key, keywords.stream().map(o -> '"' + o + '"').collect(joining(", "))));
}
-
}
diff --git a/core/src/main/java/cucumber/runtime/Shellwords.java b/core/src/main/java/io/cucumber/core/options/ShellWords.java
similarity index 72%
rename from core/src/main/java/cucumber/runtime/Shellwords.java
rename to core/src/main/java/io/cucumber/core/options/ShellWords.java
index 5835b9078b..c6583e49c3 100644
--- a/core/src/main/java/cucumber/runtime/Shellwords.java
+++ b/core/src/main/java/io/cucumber/core/options/ShellWords.java
@@ -1,18 +1,18 @@
-package cucumber.runtime;
+package io.cucumber.core.options;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-public class Shellwords {
+class ShellWords {
private static final Pattern SHELLWORDS_PATTERN = Pattern.compile("[^\\s'\"]+|[']([^']*)[']|[\"]([^\"]*)[\"]");
- private Shellwords() {
+ private ShellWords() {
}
- public static List parse(String cmdline) {
- List matchList = new ArrayList();
+ static List parse(String cmdline) {
+ List matchList = new ArrayList<>();
Matcher shellwordsMatcher = SHELLWORDS_PATTERN.matcher(cmdline);
while (shellwordsMatcher.find()) {
if (shellwordsMatcher.group(1) != null) {
@@ -20,8 +20,8 @@ public static List parse(String cmdline) {
} else {
String shellword = shellwordsMatcher.group();
if (shellword.startsWith("\"")
- && shellword.endsWith("\"")
- && shellword.length() > 2) {
+ && shellword.endsWith("\"")
+ && shellword.length() > 2) {
shellword = shellword.substring(1, shellword.length() - 1);
}
matchList.add(shellword);
diff --git a/core/src/main/java/cucumber/runtime/order/PickleOrder.java b/core/src/main/java/io/cucumber/core/order/PickleOrder.java
similarity index 83%
rename from core/src/main/java/cucumber/runtime/order/PickleOrder.java
rename to core/src/main/java/io/cucumber/core/order/PickleOrder.java
index db0f282a8a..6b1268e6ab 100644
--- a/core/src/main/java/cucumber/runtime/order/PickleOrder.java
+++ b/core/src/main/java/io/cucumber/core/order/PickleOrder.java
@@ -1,4 +1,4 @@
-package cucumber.runtime.order;
+package io.cucumber.core.order;
import java.util.List;
diff --git a/core/src/main/java/cucumber/runtime/order/StandardPickleOrders.java b/core/src/main/java/io/cucumber/core/order/StandardPickleOrders.java
similarity index 97%
rename from core/src/main/java/cucumber/runtime/order/StandardPickleOrders.java
rename to core/src/main/java/io/cucumber/core/order/StandardPickleOrders.java
index 21be80004c..1bd662f92f 100644
--- a/core/src/main/java/cucumber/runtime/order/StandardPickleOrders.java
+++ b/core/src/main/java/io/cucumber/core/order/StandardPickleOrders.java
@@ -1,4 +1,4 @@
-package cucumber.runtime.order;
+package io.cucumber.core.order;
import gherkin.events.PickleEvent;
diff --git a/core/src/main/java/io/cucumber/core/plugin/AnsiEscapes.java b/core/src/main/java/io/cucumber/core/plugin/AnsiEscapes.java
new file mode 100644
index 0000000000..eeb675e8c2
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/plugin/AnsiEscapes.java
@@ -0,0 +1,48 @@
+package io.cucumber.core.plugin;
+
+final class AnsiEscapes {
+ private static final char ESC = 27;
+ private static final char BRACKET = '[';
+
+ static AnsiEscapes RESET = color(0);
+ static AnsiEscapes BLACK = color(30);
+ static AnsiEscapes RED = color(31);
+ static AnsiEscapes GREEN = color(32);
+ static AnsiEscapes YELLOW = color(33);
+ static AnsiEscapes BLUE = color(34);
+ static AnsiEscapes MAGENTA = color(35);
+ static AnsiEscapes CYAN = color(36);
+ static AnsiEscapes WHITE = color(37);
+ static AnsiEscapes DEFAULT = color(9);
+ static AnsiEscapes GREY = color(90);
+ static AnsiEscapes INTENSITY_BOLD = color(1);
+
+ private static AnsiEscapes color(int code) {
+ return new AnsiEscapes(code + "m");
+ }
+
+ static AnsiEscapes up(int count) {
+ return new AnsiEscapes(count + "A");
+ }
+
+ private final String value;
+
+ private AnsiEscapes(String value) {
+ this.value = value;
+ }
+
+ void appendTo(NiceAppendable a) {
+ a.append(ESC).append(BRACKET).append(value);
+ }
+
+ void appendTo(StringBuilder a) {
+ a.append(ESC).append(BRACKET).append(value);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ appendTo(sb);
+ return sb.toString();
+ }
+}
diff --git a/core/src/main/java/cucumber/runtime/formatter/AnsiFormats.java b/core/src/main/java/io/cucumber/core/plugin/AnsiFormats.java
similarity index 96%
rename from core/src/main/java/cucumber/runtime/formatter/AnsiFormats.java
rename to core/src/main/java/io/cucumber/core/plugin/AnsiFormats.java
index cd8e30c9bc..3db1b520c8 100644
--- a/core/src/main/java/cucumber/runtime/formatter/AnsiFormats.java
+++ b/core/src/main/java/io/cucumber/core/plugin/AnsiFormats.java
@@ -1,6 +1,4 @@
-package cucumber.runtime.formatter;
-
-import cucumber.api.formatter.AnsiEscapes;
+package io.cucumber.core.plugin;
import java.util.HashMap;
import java.util.Map;
diff --git a/core/src/main/java/cucumber/api/event/CanonicalEventOrder.java b/core/src/main/java/io/cucumber/core/plugin/CanonicalEventOrder.java
similarity index 70%
rename from core/src/main/java/cucumber/api/event/CanonicalEventOrder.java
rename to core/src/main/java/io/cucumber/core/plugin/CanonicalEventOrder.java
index 240fcf3d8c..dd78fe5858 100644
--- a/core/src/main/java/cucumber/api/event/CanonicalEventOrder.java
+++ b/core/src/main/java/io/cucumber/core/plugin/CanonicalEventOrder.java
@@ -1,10 +1,42 @@
-package cucumber.api.event;
+package io.cucumber.core.plugin;
+
+import io.cucumber.core.event.Event;
+import io.cucumber.core.event.SnippetsSuggestedEvent;
+import io.cucumber.core.event.StepDefinedEvent;
+import io.cucumber.core.event.TestCaseEvent;
+import io.cucumber.core.event.TestRunFinished;
+import io.cucumber.core.event.TestRunStarted;
+import io.cucumber.core.event.TestSourceRead;
import java.util.Comparator;
import java.util.List;
import static java.util.Arrays.asList;
+/**
+ * When pickles are executed in parallel events can be
+ * produced with a partial ordering.
+ *
+ * The canonical order is the order in which these events
+ * would have been generated had cucumber executed these
+ * pickles in a serial fashion.
+ *
+ * In canonical order events are first ordered by type:
+ *
+ * - TestRunStarted
+ *
- TestSourceRead
+ *
- SnippetsSuggestedEvent
+ *
- TestCaseEvent
+ *
- TestRunFinished
+ *
+ *
+ * Then TestCaseEvents are ordered by
+ *
+ * - uri
+ *
- line
+ *
- timestamp
+ *
+ */
final class CanonicalEventOrder implements Comparator {
private static final FixedEventOrderComparator fixedOrder = new FixedEventOrderComparator();
@@ -27,8 +59,7 @@ public int compare(Event a, Event b) {
private static final class FixedEventOrderComparator implements Comparator {
private final List> fixedOrder = asList(
- (Class extends Event>)
- TestRunStarted.class,
+ TestRunStarted.class,
TestSourceRead.class,
SnippetsSuggestedEvent.class,
StepDefinedEvent.class,
@@ -73,7 +104,7 @@ public int compare(TestCaseEvent a, TestCaseEvent b) {
return line;
}
- return Long.compare(a.getTimeStamp(), b.getTimeStamp());
+ return a.getInstant().compareTo(b.getInstant());
}
}
}
diff --git a/core/src/main/java/cucumber/runner/CanonicalOrderEventPublisher.java b/core/src/main/java/io/cucumber/core/plugin/CanonicalOrderEventPublisher.java
similarity index 50%
rename from core/src/main/java/cucumber/runner/CanonicalOrderEventPublisher.java
rename to core/src/main/java/io/cucumber/core/plugin/CanonicalOrderEventPublisher.java
index 712df8077c..8562b027d5 100644
--- a/core/src/main/java/cucumber/runner/CanonicalOrderEventPublisher.java
+++ b/core/src/main/java/io/cucumber/core/plugin/CanonicalOrderEventPublisher.java
@@ -1,20 +1,20 @@
-package cucumber.runner;
+package io.cucumber.core.plugin;
-import cucumber.api.event.Event;
-import cucumber.api.event.TestRunFinished;
+import io.cucumber.core.event.Event;
+import io.cucumber.core.event.TestRunFinished;
+import io.cucumber.core.eventbus.AbstractEventPublisher;
-import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
-public class CanonicalOrderEventPublisher extends AbstractEventPublisher {
+final class CanonicalOrderEventPublisher extends AbstractEventPublisher {
private final List queue = new LinkedList<>();
public void handle(final Event event) {
queue.add(event);
if (event instanceof TestRunFinished) {
- Collections.sort(queue, Event.CANONICAL_ORDER);
+ queue.sort(new CanonicalEventOrder());
sendAll(queue);
queue.clear();
}
diff --git a/core/src/main/java/cucumber/api/formatter/ColorAware.java b/core/src/main/java/io/cucumber/core/plugin/ColorAware.java
similarity index 81%
rename from core/src/main/java/cucumber/api/formatter/ColorAware.java
rename to core/src/main/java/io/cucumber/core/plugin/ColorAware.java
index 1bbce1521c..ec0d626ff5 100644
--- a/core/src/main/java/cucumber/api/formatter/ColorAware.java
+++ b/core/src/main/java/io/cucumber/core/plugin/ColorAware.java
@@ -1,10 +1,11 @@
-package cucumber.api.formatter;
+package io.cucumber.core.plugin;
-import cucumber.api.Plugin;
+import org.apiguardian.api.API;
/**
* Interface for Plugins that use ANSI escape codes to print coloured output.
*/
+@API(status = API.Status.STABLE)
public interface ColorAware extends Plugin {
/**
* When set to monochrome the plugin should not use colored output.
diff --git a/core/src/main/java/cucumber/api/event/ConcurrentEventListener.java b/core/src/main/java/io/cucumber/core/plugin/ConcurrentEventListener.java
similarity index 75%
rename from core/src/main/java/cucumber/api/event/ConcurrentEventListener.java
rename to core/src/main/java/io/cucumber/core/plugin/ConcurrentEventListener.java
index 8a01204c8a..c3118a2151 100644
--- a/core/src/main/java/cucumber/api/event/ConcurrentEventListener.java
+++ b/core/src/main/java/io/cucumber/core/plugin/ConcurrentEventListener.java
@@ -1,6 +1,9 @@
-package cucumber.api.event;
+package io.cucumber.core.plugin;
-import cucumber.api.Plugin;
+import io.cucumber.core.event.Event;
+import io.cucumber.core.event.EventPublisher;
+import io.cucumber.core.event.TestCase;
+import org.apiguardian.api.API;
/**
* Listens to pickle execution events. Can be used to
@@ -8,7 +11,7 @@
*
* When cucumber executes test in parallel or in a framework
* that supports parallel execution (e.g. JUnit or TestNG)
- * {@link cucumber.api.TestCase} events from different
+ * {@link TestCase} events from different
* pickles may interleave.
*
* This interface marks an {@link EventListener} as capable of
@@ -29,10 +32,11 @@
*
* @see Event
*/
+@API(status = API.Status.STABLE)
public interface ConcurrentEventListener extends Plugin {
/**
- * Set the event publisher. The formatter can register event listeners with the publisher.
+ * Set the event publisher. The plugin can register event listeners with the publisher.
*
* @param publisher the event publisher
*/
diff --git a/core/src/main/java/cucumber/runtime/formatter/DefaultSummaryPrinter.java b/core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java
similarity index 78%
rename from core/src/main/java/cucumber/runtime/formatter/DefaultSummaryPrinter.java
rename to core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java
index e8457edc7e..6cd155ee0f 100644
--- a/core/src/main/java/cucumber/runtime/formatter/DefaultSummaryPrinter.java
+++ b/core/src/main/java/io/cucumber/core/plugin/DefaultSummaryPrinter.java
@@ -1,18 +1,13 @@
-package cucumber.runtime.formatter;
+package io.cucumber.core.plugin;
-import cucumber.api.SummaryPrinter;
-import cucumber.api.event.EventHandler;
-import cucumber.api.event.EventListener;
-import cucumber.api.event.EventPublisher;
-import cucumber.api.event.TestRunFinished;
-import cucumber.api.formatter.ColorAware;
-import cucumber.api.formatter.StrictAware;
-import cucumber.runtime.UndefinedStepsTracker;
+import io.cucumber.core.event.EventHandler;
+import io.cucumber.core.event.EventPublisher;
+import io.cucumber.core.event.TestRunFinished;
import java.io.PrintStream;
import java.util.List;
-class DefaultSummaryPrinter implements SummaryPrinter, ColorAware, StrictAware, EventListener {
+public final class DefaultSummaryPrinter implements SummaryPrinter, ColorAware, StrictAware, EventListener {
private final Stats stats = new Stats();
private final UndefinedStepsTracker undefinedStepsTracker = new UndefinedStepsTracker();
diff --git a/core/src/main/java/cucumber/api/event/EventListener.java b/core/src/main/java/io/cucumber/core/plugin/EventListener.java
similarity index 63%
rename from core/src/main/java/cucumber/api/event/EventListener.java
rename to core/src/main/java/io/cucumber/core/plugin/EventListener.java
index 614a62d497..f23b21fb38 100644
--- a/core/src/main/java/cucumber/api/event/EventListener.java
+++ b/core/src/main/java/io/cucumber/core/plugin/EventListener.java
@@ -1,6 +1,8 @@
-package cucumber.api.event;
+package io.cucumber.core.plugin;
-import cucumber.api.Plugin;
+import io.cucumber.core.event.Event;
+import io.cucumber.core.event.EventPublisher;
+import org.apiguardian.api.API;
/**
* Listens to pickle execution events. Can be used to
@@ -8,13 +10,14 @@
*
* When cucumber executes test in parallel or in a framework
* that supports parallel execution (e.g. JUnit or TestNG)
- * {@link cucumber.api.event.Event}s are stored and published
- * in @{@link Event#CANONICAL_ORDER} after the test run has
+ * {@link Event}s are stored and published
+ * in @{@link io.cucumber.core.plugin.CanonicalEventOrder} after the test run has
* completed.
*
* @see Event
* @see ConcurrentEventListener
*/
+@API(status = API.Status.STABLE)
public interface EventListener extends Plugin {
/**
diff --git a/core/src/main/java/io/cucumber/core/plugin/Format.java b/core/src/main/java/io/cucumber/core/plugin/Format.java
new file mode 100644
index 0000000000..272c39c075
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/plugin/Format.java
@@ -0,0 +1,5 @@
+package io.cucumber.core.plugin;
+
+interface Format {
+ String text(String text);
+}
diff --git a/core/src/main/java/io/cucumber/core/plugin/Formats.java b/core/src/main/java/io/cucumber/core/plugin/Formats.java
new file mode 100644
index 0000000000..3d4e046928
--- /dev/null
+++ b/core/src/main/java/io/cucumber/core/plugin/Formats.java
@@ -0,0 +1,7 @@
+package io.cucumber.core.plugin;
+
+interface Formats {
+ Format get(String key);
+
+ String up(int n);
+}
diff --git a/core/src/main/java/cucumber/runtime/formatter/HTMLFormatter.java b/core/src/main/java/io/cucumber/core/plugin/HTMLFormatter.java
similarity index 76%
rename from core/src/main/java/cucumber/runtime/formatter/HTMLFormatter.java
rename to core/src/main/java/io/cucumber/core/plugin/HTMLFormatter.java
index 66c94a1c38..8f7c599cc3 100644
--- a/core/src/main/java/cucumber/runtime/formatter/HTMLFormatter.java
+++ b/core/src/main/java/io/cucumber/core/plugin/HTMLFormatter.java
@@ -1,21 +1,5 @@
-package cucumber.runtime.formatter;
-
-import cucumber.api.HookTestStep;
-import cucumber.api.PickleStepTestStep;
-import cucumber.api.Result;
-import cucumber.api.TestCase;
-import cucumber.api.event.EmbedEvent;
-import cucumber.api.event.EventHandler;
-import cucumber.api.event.EventListener;
-import cucumber.api.event.EventPublisher;
-import cucumber.api.event.TestCaseStarted;
-import cucumber.api.event.TestRunFinished;
-import cucumber.api.event.TestSourceRead;
-import cucumber.api.event.TestStepFinished;
-import cucumber.api.event.TestStepStarted;
-import cucumber.api.event.WriteEvent;
-import cucumber.api.formatter.NiceAppendable;
-import cucumber.runtime.CucumberException;
+package io.cucumber.core.plugin;
+
import gherkin.ast.Background;
import gherkin.ast.DataTable;
import gherkin.ast.DocString;
@@ -35,7 +19,21 @@
import gherkin.pickles.PickleRow;
import gherkin.pickles.PickleString;
import gherkin.pickles.PickleTable;
-import gherkin.pickles.PickleTag;
+import io.cucumber.core.event.EmbedEvent;
+import io.cucumber.core.event.EventHandler;
+import io.cucumber.core.event.EventPublisher;
+import io.cucumber.core.event.HookTestStep;
+import io.cucumber.core.event.HookType;
+import io.cucumber.core.event.PickleStepTestStep;
+import io.cucumber.core.event.Result;
+import io.cucumber.core.event.TestCase;
+import io.cucumber.core.event.TestCaseStarted;
+import io.cucumber.core.event.TestRunFinished;
+import io.cucumber.core.event.TestSourceRead;
+import io.cucumber.core.event.TestStepFinished;
+import io.cucumber.core.event.TestStepStarted;
+import io.cucumber.core.event.WriteEvent;
+import io.cucumber.core.exception.CucumberException;
import java.io.Closeable;
import java.io.File;
@@ -43,22 +41,27 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-final class HTMLFormatter implements EventListener {
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Locale.ROOT;
+
+public final class HTMLFormatter implements EventListener {
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
private static final String JS_FORMATTER_VAR = "formatter";
private static final String JS_REPORT_FILENAME = "report.js";
private static final String[] TEXT_ASSETS = new String[]{
- "/io/cucumber/formatter/html/formatter.js",
- "/io/cucumber/formatter/html/index.html",
- "/io/cucumber/formatter/html/jquery-1.8.2.min.js",
- "/io/cucumber/formatter/html/style.css"
- };
+ "/io/cucumber/core/plugin/html/formatter.js",
+ "/io/cucumber/core/plugin/html/index.html",
+ "/io/cucumber/core/plugin/html/jquery-1.8.2.min.js",
+ "/io/cucumber/core/plugin/html/style.css"
+ };
private static final Map MIME_TYPES_EXTENSIONS = new HashMap() {
{
put("image/bmp", "bmp");
@@ -87,7 +90,7 @@ public void receive(TestSourceRead event) {
handleTestSourceRead(event);
}
};
- private EventHandler caseStartedHandler= new EventHandler() {
+ private EventHandler caseStartedHandler = new EventHandler