Skip to content

Commit

Permalink
[Java] Add DataTableTypeDefinition
Browse files Browse the repository at this point in the history
  • Loading branch information
mpkorstanje committed Jul 12, 2019
1 parent 2e89b78 commit 6b3b763
Show file tree
Hide file tree
Showing 17 changed files with 341 additions and 87 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.cucumber.core.backend;

import io.cucumber.datatable.DataTableType;
import org.apiguardian.api.API;

@API(status = API.Status.EXPERIMENTAL)
public interface DataTableTypeTypeDefinition {

DataTableType dataTableType();

}
3 changes: 3 additions & 0 deletions core/src/main/java/io/cucumber/core/backend/Glue.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.cucumber.core.backend;

import io.cucumber.core.stepexpression.TypeRegistry;
import io.cucumber.datatable.DataTableType;
import org.apiguardian.api.API;

import java.util.function.Function;
Expand All @@ -20,4 +21,6 @@ public interface Glue {

void addParameterType(ParameterTypeDefinition parameterTypeDefinition);

void addDataTableType(DataTableTypeTypeDefinition dataTableTypeTypeDefinition);

}
8 changes: 7 additions & 1 deletion core/src/main/java/io/cucumber/core/runner/CachingGlue.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.cucumber.core.runner;

import io.cucumber.core.backend.DataTableTypeTypeDefinition;
import io.cucumber.core.backend.ParameterTypeDefinition;
import io.cucumber.core.event.StepDefinedEvent;
import io.cucumber.core.backend.DuplicateStepDefinitionException;
Expand Down Expand Up @@ -86,6 +87,11 @@ public void addParameterType(ParameterTypeDefinition parameterTypeDefinition) {
typeRegistry.defineParameterType(parameterTypeDefinition.parameterType());
}

@Override
public void addDataTableType(DataTableTypeTypeDefinition dataTableTypeTypeDefinition) {
typeRegistry.defineDataTableType(dataTableTypeTypeDefinition.dataTableType());
}

List<HookDefinition> getBeforeHooks() {
return new ArrayList<>(beforeHooks);
}
Expand Down Expand Up @@ -131,7 +137,7 @@ PickleStepDefinitionMatch stepDefinitionMatch(String featurePath, PickleStep ste
}

private List<PickleStepDefinitionMatch> stepDefinitionMatches(String featurePath, PickleStep step) {
List<PickleStepDefinitionMatch> result = new ArrayList<PickleStepDefinitionMatch>();
List<PickleStepDefinitionMatch> result = new ArrayList<>();
for (StepDefinition stepDefinition : stepDefinitionsByPattern.values()) {
List<Argument> arguments = stepDefinition.matchedArguments(step);
if (arguments != null) {
Expand Down
15 changes: 9 additions & 6 deletions core/src/main/java/io/cucumber/core/runner/Runner.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package io.cucumber.core.runner;

import io.cucumber.core.event.HookType;
import io.cucumber.core.event.SnippetsSuggestedEvent;
import gherkin.events.PickleEvent;
import gherkin.pickles.PickleStep;
import gherkin.pickles.PickleTag;
import io.cucumber.core.backend.Backend;
import io.cucumber.core.backend.HookDefinition;
import io.cucumber.core.backend.ObjectFactory;
import io.cucumber.core.event.HookType;
import io.cucumber.core.event.SnippetsSuggestedEvent;
import io.cucumber.core.eventbus.EventBus;
import io.cucumber.core.logging.Logger;
import io.cucumber.core.logging.LoggerFactory;
Expand Down Expand Up @@ -57,10 +57,13 @@ public EventBus getBus() {
}

public void runPickle(PickleEvent pickle) {
buildBackendWorlds(); // Java8 step definitions will be added to the glue here
TestCase testCase = createTestCaseForPickle(pickle);
testCase.run(bus);
disposeBackendWorlds();
try {
buildBackendWorlds(); // Java8 step definitions will be added to the glue here
TestCase testCase = createTestCaseForPickle(pickle);
testCase.run(bus);
} finally {
disposeBackendWorlds();
}
}

private TestCase createTestCaseForPickle(PickleEvent pickleEvent) {
Expand Down
23 changes: 23 additions & 0 deletions java/src/main/java/io/cucumber/java/DataTableType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.cucumber.java;

import org.apiguardian.api.API;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Allows a DataTableType to be registered.
*
* Supports TableCellTransformer: String -> T
* Supports TableEntryTransformer: Map<String, String> -> T
* Supports TableRowTransformer: List<String> -> T
* Supports TableTransformer: DataTable -> T
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@API(status = API.Status.STABLE)
public @interface DataTableType {

}
2 changes: 2 additions & 0 deletions java/src/main/java/io/cucumber/java/JavaBackend.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ void addHook(Annotation annotation, Method method) {
boolean useForSnippets = parameterType.useForSnippets();
boolean preferForRegexMatch = parameterType.preferForRegexMatch();
glue.addParameterType(new JavaParameterTypeDefinition(name, pattern, method, useForSnippets, preferForRegexMatch, lookup));
} else if (annotation.annotationType().equals(DataTableType.class)) {
glue.addDataTableType(new JavaDataTableTypeDefinition(method, lookup));
}
}
}
Expand Down
122 changes: 122 additions & 0 deletions java/src/main/java/io/cucumber/java/JavaDataTableTypeDefinition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package io.cucumber.java;

import io.cucumber.core.backend.DataTableTypeTypeDefinition;
import io.cucumber.core.backend.Lookup;
import io.cucumber.core.exception.CucumberException;
import io.cucumber.core.runtime.Invoker;
import io.cucumber.datatable.DataTable;
import io.cucumber.datatable.DataTableType;
import io.cucumber.datatable.TableCellTransformer;
import io.cucumber.datatable.TableEntryTransformer;
import io.cucumber.datatable.TableRowTransformer;
import io.cucumber.datatable.TableTransformer;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

class JavaDataTableTypeDefinition implements DataTableTypeTypeDefinition {

private final Method method;
private final Lookup lookup;
private final DataTableType dataTableType;

JavaDataTableTypeDefinition(Method method, Lookup lookup) {
this.method = method;
this.lookup = lookup;
this.dataTableType = createDataTableType(method);
}

@SuppressWarnings("unchecked")
private DataTableType createDataTableType(Method method) {
Class returnType = requireValidReturnType(method);
Type parameterType = requireValidParameterType(method);

if (DataTable.class.equals(parameterType)) {
return new DataTableType(
returnType,
(TableTransformer<Object>) this::execute
);
}

if (List.class.equals(parameterType)) {
return new DataTableType(
returnType,
(TableRowTransformer<Object>) this::execute
);
}

if (Map.class.equals(parameterType)) {
return new DataTableType(
returnType,
(TableEntryTransformer<Object>) this::execute
);
}

if (String.class.equals(parameterType)) {
return new DataTableType(
returnType,
(TableCellTransformer<Object>) this::execute
);
}

throw createInvalidSignatureException();

}

private static CucumberException createInvalidSignatureException() {
return new CucumberException("" +
"A @DataTableType annotated method must have one of these signatures:\n" +
" * public Author author(DataTable table)\n" +
" * public Author author(List<String> row)\n" +
" * public Author author(Map<String, String> entry)\n" +
" * public Author author(String cell)\n" +
"Note: Author is an example of the class you want to convert the table to"
);
}


private static Type requireValidParameterType(Method method) {
Type[] parameterTypes = method.getGenericParameterTypes();
if (parameterTypes.length != 1) {
throw createInvalidSignatureException();
}

Type parameterType = parameterTypes[0];
if (!(parameterType instanceof ParameterizedType)) {
return parameterType;
}

ParameterizedType parameterizedType = (ParameterizedType) parameterType;
Type[] typeParameters = parameterizedType.getActualTypeArguments();
for (Type typeParameter : typeParameters) {
if (!String.class.equals(typeParameter)) {
throw createInvalidSignatureException();
}
}

return parameterizedType.getRawType();
}

private static Class requireValidReturnType(Method method) {
Class returnType = method.getReturnType();
if (Void.class.equals(returnType)) {
throw createInvalidSignatureException();
}
return returnType;
}


@Override
public DataTableType dataTableType() {
return dataTableType;
}


private Object execute(Object arg) throws Throwable {
return Invoker.invoke(lookup.getInstance(method.getDeclaringClass()), method, 0, arg);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,58 +8,60 @@

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;

public class JavaParameterTypeDefinition implements ParameterTypeDefinition {
class JavaParameterTypeDefinition implements ParameterTypeDefinition {

private final String name;
private final List<String> patterns;
private final Method method;

private final Lookup lookup;
private final boolean preferForRegexpMatch;
private final boolean useForSnippets;
private final ParameterType<Object> parameterType;

JavaParameterTypeDefinition(String name, String pattern, Method method, boolean useForSnippets, boolean preferForRegexpMatch, Lookup lookup) {
this.name = name.isEmpty() ? method.getName() : name;
this.patterns = Collections.singletonList(pattern);
this.method = requireValidMethod(method);
this.lookup = lookup;
this.useForSnippets = useForSnippets;
this.preferForRegexpMatch = preferForRegexpMatch;
this.parameterType = new ParameterType<>(
name.isEmpty() ? method.getName() : name,
Collections.singletonList(pattern),
this.method.getReturnType(),
this::execute,
useForSnippets,
preferForRegexpMatch
);
}

private Method requireValidMethod(Method method) {
Class<?> returnType = method.getReturnType();
if (Void.class.equals(returnType)) {
throw new CucumberException("TODO");
throw createInvalidSignatureException();
}

Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length < 1) {
throw new CucumberException("TODO");
throw createInvalidSignatureException();
}

for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
for (Class<?> parameterType : parameterTypes) {
if (!String.class.equals(parameterType)) {
throw new CucumberException("TODO" + i);
throw createInvalidSignatureException();
}
}

return method;
}

private CucumberException createInvalidSignatureException() {
return new CucumberException("" +
"A @ParameterType annotated method must have one of these signatures:\n" +
" * public Author parameterName(String all)\n" +
" * public Author parameterName(String captureGroup1, String captureGroup2, ...ect )\n" +
" * public Author parameterName(String... captureGroups)\n" +
"Note: Author is an example of the class you want to convert parameter name"
);
}

@Override
public ParameterType<?> parameterType() {
return new ParameterType<>(
name,
patterns,
method.getReturnType(),
this::execute,
useForSnippets,
preferForRegexpMatch
);
return parameterType;
}

private Object execute(Object[] args) throws Throwable {
Expand Down
1 change: 1 addition & 0 deletions java/src/main/java/io/cucumber/java/MethodScanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ private boolean isHookAnnotation(Annotation annotation) {
|| annotationClass.equals(BeforeStep.class)
|| annotationClass.equals(AfterStep.class)
|| annotationClass.equals(ParameterType.class)
|| annotationClass.equals(DataTableType.class)
;
}

Expand Down
6 changes: 6 additions & 0 deletions java/src/main/java/io/cucumber/java/ParameterType.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Defines a parameter type.
*
* Method signature must have a String argument for each capture group
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@API(status = API.Status.STABLE)
Expand Down
Loading

0 comments on commit 6b3b763

Please sign in to comment.