Skip to content

[Java8] Add lambda parameter type annotations #1768

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 42 commits into from
Oct 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
c1c33a9
WIP Lambda equivalent for @DocStringType annotation
Sep 10, 2019
ca66872
Implemented addDocStringType method in tests mocked glue
Sep 10, 2019
a141a02
Lambda equivalent for @DataTableType annotation
Sep 12, 2019
17b32e1
Lambda equivalent for @ParameterType annotation
Sep 16, 2019
7685c69
basic tests
Sep 18, 2019
0e27023
Fixed broken data table lambda registration
Sep 20, 2019
9251ef0
more tests
Sep 23, 2019
b8e44dd
Merge remote-tracking branch 'origin/master' into
Sep 26, 2019
edb0b77
Remove unused timeout
Sep 26, 2019
ecb8424
Work in progress; Added ParameterType with two arguments (broken still)
Sep 27, 2019
350c91b
Alter example to do away with Point class
Sep 27, 2019
529c21c
Switch to StringBuffer to actually return a different type
Sep 27, 2019
59960fb
Expand multi argument example to three
Sep 27, 2019
c097b5a
Add example that maps to Map.Entry
Sep 27, 2019
d9e3816
Demonstrate issues parsing any non-string arguments
Sep 27, 2019
c0efd78
One step closer
Sep 27, 2019
0e0dcce
Only support String arguments after looking at Java & Expressions
Sep 27, 2019
fdefbf4
Remove Class<T> bodyClass arg as we're only using first type argument
Sep 27, 2019
de71b15
Add ParameterTypes with 4-9 arguments
Sep 27, 2019
3099f1b
Sprinkle on a few more generics
Sep 27, 2019
3ef30f5
Add more LambdaGlue
Sep 27, 2019
fef6bc1
Do not reintroduce AfterStep with timeout argument
Sep 27, 2019
d12aa24
First JavaDoc
Sep 27, 2019
f9ac282
Add more JavaDoc
Sep 27, 2019
9265fcd
Fix compilation error in Travis
Sep 27, 2019
04ff667
Map.Entry is only in Java11; so drop that test
Sep 27, 2019
6ad67f8
Harmonize project names in Maven output
Sep 28, 2019
36fd570
Remove left over Map.Entry scenario
Sep 28, 2019
e727622
Remove generics from ParameterTypeDefinition & ParameterDefinitionBody
Sep 28, 2019
5dd201f
Merge pull request #1782 from cucumber/lambda-type-annotations-continued
mpkorstanje Sep 28, 2019
5feea6f
Rename as suggested in review comments
Sep 30, 2019
8a79d35
Remove unused imports
Sep 30, 2019
a2cb66f
Add our own Point class to prevent needing java.awt in Java 12+
Sep 30, 2019
4538a81
Add a few of the missing supported types
Sep 30, 2019
c83556a
Lambda docstring content type checks
Oct 7, 2019
347d169
Removed unused API annotation, because constructors is package private
Oct 7, 2019
3da65e6
Lambda table row definition
Oct 7, 2019
c3d0e07
Lambda table row definition
Oct 7, 2019
4d22606
Catch doc string exceptions
mpkorstanje Oct 7, 2019
b765883
Lambda table definition, Lambda cell definition
Oct 9, 2019
33a7bda
Fixed broken javadoc
Oct 9, 2019
0dd6ffd
Fixed broken javadoc
Oct 9, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.cucumber.cucumberexpressions.CucumberExpressionException;
import io.cucumber.datatable.CucumberDataTableException;
import io.cucumber.datatable.UndefinedDataTableTypeException;
import io.cucumber.docstring.CucumberDocStringException;

import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -44,7 +45,7 @@ public void runStep(TestCaseState state) throws Throwable {
}
} catch (UndefinedDataTableTypeException e) {
throw registerTypeInConfiguration(e);
} catch (CucumberExpressionException | CucumberDataTableException e) {
} catch (CucumberExpressionException | CucumberDataTableException | CucumberDocStringException e) {
throw couldNotConvertArguments(e);
} catch (CucumberBackendException e) {
throw couldNotInvokeArgumentConversion(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import io.cucumber.core.stepexpression.StepTypeRegistry;
import io.cucumber.cucumberexpressions.ParameterType;
import io.cucumber.datatable.DataTableType;
import io.cucumber.docstring.DocStringType;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;

Expand Down Expand Up @@ -240,6 +241,36 @@ void throws_could_not_convert_exception_for_singleton_table_dimension_mismatch()
)));
}

@Test
void throws_could_not_convert_exception_for_docstring() {
CucumberFeature feature = TestFeatureParser.parse("" +
"Feature: Test feature\n" +
" Scenario: Test scenario\n" +
" Given I have some cukes in my belly\n" +
" \"\"\"doc\n" +
" converting this should throw an exception\n" +
" \"\"\"\n"
);

stepTypeRegistry.defineDocStringType(new DocStringType(ItemQuantity.class, "doc", content -> {
throw new IllegalArgumentException(content);
}));

CucumberStep step = feature.getPickles().get(0).getSteps().get(0);
StepDefinition stepDefinition = new StubStepDefinition("I have some cukes in my belly", ItemQuantity.class);
CoreStepDefinition coreStepDefinition = new CoreStepDefinition(stepDefinition, stepTypeRegistry);
List<Argument> arguments = coreStepDefinition.matchedArguments(step);

StepDefinitionMatch stepDefinitionMatch = new PickleStepDefinitionMatch(arguments, stepDefinition, null, step);

Executable testMethod = () -> stepDefinitionMatch.runStep(null);
CucumberException actualThrown = assertThrows(CucumberException.class, testMethod);
assertThat(actualThrown.getMessage(), is(equalTo(
"Could not convert arguments for step [I have some cukes in my belly] defined at '{stubbed location with details}'.\n" +
"The details are in the stacktrace below."
)));
}

@Test
void throws_could_not_invoke_argument_conversion_when_argument_could_not_be_got() {
CucumberFeature feature = TestFeatureParser.parse("file:test.feature", "" +
Expand Down
2 changes: 1 addition & 1 deletion docstring/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<artifactId>docstring</artifactId>
<packaging>jar</packaging>
<name>Docstring</name>
<name>Cucumber-JVM: Docstring</name>

<properties>
<project.Automatic-Module-Name>io.cucumber.docstring</project.Automatic-Module-Name>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.cucumber.java8;

import org.apiguardian.api.API;

@FunctionalInterface
@API(status = API.Status.STABLE)
public interface DataTableCellDefinitionBody<T> {
T accept(String cell) throws Throwable;
}
10 changes: 10 additions & 0 deletions java8/src/main/java/io/cucumber/java8/DataTableDefinitionBody.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.cucumber.java8;

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

@FunctionalInterface
@API(status = API.Status.STABLE)
public interface DataTableDefinitionBody<T> {
T accept(DataTable dataTable) throws Throwable;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.cucumber.java8;

import java.util.Map;
import org.apiguardian.api.API;

@FunctionalInterface
@API(status = API.Status.STABLE)
public interface DataTableEntryDefinitionBody<T> {
T accept(Map<String, String> map) throws Throwable;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.cucumber.java8;

import java.util.List;
import org.apiguardian.api.API;

@FunctionalInterface
@API(status = API.Status.STABLE)
public interface DataTableRowDefinitionBody<T> {
T accept(List<String> row) throws Throwable;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.cucumber.java8;

import org.apiguardian.api.API;

@FunctionalInterface
@API(status = API.Status.STABLE)
public interface DocStringDefinitionBody<T> {
T accept(String docString) throws Throwable;
}
18 changes: 18 additions & 0 deletions java8/src/main/java/io/cucumber/java8/Java8Backend.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

import io.cucumber.core.backend.Backend;
import io.cucumber.core.backend.Container;
import io.cucumber.core.backend.DataTableTypeDefinition;
import io.cucumber.core.backend.DocStringTypeDefinition;
import io.cucumber.core.backend.Glue;
import io.cucumber.core.backend.HookDefinition;
import io.cucumber.core.backend.Lookup;
import io.cucumber.core.backend.ParameterTypeDefinition;
import io.cucumber.core.backend.Snippet;
import io.cucumber.core.backend.StepDefinition;
import io.cucumber.core.io.ClassFinder;
Expand Down Expand Up @@ -105,7 +108,22 @@ public void addBeforeHookDefinition(HookDefinition beforeHook) {
@Override
public void addAfterHookDefinition(HookDefinition afterHook) {
glue.addAfterHook(afterHook);
}

@Override
public void addDocStringType(DocStringTypeDefinition docStringTypeDefinition) {
glue.addDocStringType(docStringTypeDefinition);

}

@Override
public void addDataTableType(DataTableTypeDefinition dataTableTypeDefinition) {
glue.addDataTableType(dataTableTypeDefinition);
}

@Override
public void addParameterType(ParameterTypeDefinition parameterTypeDefinition) {
glue.addParameterType(parameterTypeDefinition);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.cucumber.java8;

import io.cucumber.core.backend.DataTableTypeDefinition;
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.Type;
import java.util.List;
import java.util.Map;
import net.jodah.typetools.TypeResolver;

final class Java8DataTableTypeDefinition extends AbstractGlueDefinition implements DataTableTypeDefinition {

private final DataTableType dataTableType;

Java8DataTableTypeDefinition(Object body) {
super(body, new Exception().getStackTrace()[3]);
this.dataTableType = createDataTableType(method);
}

private DataTableType createDataTableType(Method method) {
Class returnType = TypeResolver.resolveRawArguments(DataTableEntryDefinitionBody.class, body.getClass())[0];
Type[] parameterTypes = method.getGenericParameterTypes();
Type parameterType = parameterTypes[0];

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);
}

return new DataTableType(returnType, (TableTransformer<Object>) this::execute);
}

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

private Object execute(Object arg) throws Throwable {
return Invoker.invoke(this, body, method, arg);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.cucumber.java8;

import io.cucumber.core.backend.DocStringTypeDefinition;
import io.cucumber.core.exception.CucumberException;
import io.cucumber.docstring.DocStringType;
import net.jodah.typetools.TypeResolver;

final class Java8DocStringTypeDefinition extends AbstractGlueDefinition implements DocStringTypeDefinition {

private final DocStringType docStringType;

@Override
public DocStringType docStringType() {
return docStringType;
}

Java8DocStringTypeDefinition(Object body, String contentType) {
super(body, new Exception().getStackTrace()[3]);
if (contentType == null) {
throw new CucumberException("Docstring content type couldn't be null, define docstring content type");
}
if (contentType.isEmpty()) {
throw new CucumberException("Docstring content type couldn't be empty, define docstring content type");
}
Class returnType = TypeResolver.resolveRawArguments(DocStringDefinitionBody.class, body.getClass())[0];
this.docStringType = new DocStringType(
returnType,
contentType,
this::execute);
}

private Object execute(String content) throws Throwable {
return Invoker.invoke(this, body, method, content);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.cucumber.java8;

import java.util.Collections;

import io.cucumber.core.backend.ParameterTypeDefinition;
import io.cucumber.cucumberexpressions.ParameterType;

import static net.jodah.typetools.TypeResolver.resolveRawArguments;

class Java8ParameterTypeDefinition extends AbstractGlueDefinition implements ParameterTypeDefinition {

private final ParameterType parameterType;

@Override
public ParameterType<?> parameterType() {
return parameterType;
}

<T extends ParameterDefinitionBody> Java8ParameterTypeDefinition(String name, String regex, Class<T> bodyClass, T body) {
super(body, new Exception().getStackTrace()[3]);
Class<?> returnType = resolveRawArguments(bodyClass, body.getClass())[0];
this.parameterType = new ParameterType(name, Collections.singletonList(regex), returnType, this::execute);
}

private Object execute(String[] parameterContent) throws Throwable {
return Invoker.invoke(this, body, method, parameterContent);
}
}
10 changes: 1 addition & 9 deletions java8/src/main/java/io/cucumber/java8/Java8StepDefinition.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,19 @@

final class Java8StepDefinition extends AbstractGlueDefinition implements StepDefinition {

private final long timeoutMillis;
private final List<ParameterInfo> parameterInfos;
private final String expression;
private <T extends StepDefinitionBody> Java8StepDefinition(String expression,
long timeoutMillis,
Class<T> bodyClass,
T body) {
super(body, new Exception().getStackTrace()[3]);
this.timeoutMillis = timeoutMillis;
this.expression = requireNonNull(expression, "cucumber-expression may not be null");
this.parameterInfos = fromTypes(expression, location, resolveRawArguments(bodyClass, body.getClass()));
}

public static <T extends StepDefinitionBody> Java8StepDefinition create(
String expression, Class<T> bodyClass, T body) {
return new Java8StepDefinition(expression, 0, bodyClass, body);
}

public static <T extends StepDefinitionBody> StepDefinition create(
String expression, long timeoutMillis, Class<T> bodyClass, T body) {
return new Java8StepDefinition(expression, timeoutMillis, bodyClass, body);
return new Java8StepDefinition(expression, bodyClass, body);
}

private static List<ParameterInfo> fromTypes(String expression, StackTraceElement location, Type[] genericParameterTypes) {
Expand Down
Loading