Skip to content

Commit cc1c541

Browse files
committed
[Java] Add DataTableTypeDefinition
1 parent db67385 commit cc1c541

File tree

17 files changed

+341
-87
lines changed

17 files changed

+341
-87
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.cucumber.core.backend;
2+
3+
import io.cucumber.datatable.DataTableType;
4+
import org.apiguardian.api.API;
5+
6+
@API(status = API.Status.EXPERIMENTAL)
7+
public interface DataTableTypeTypeDefinition {
8+
9+
DataTableType dataTableType();
10+
11+
}

core/src/main/java/io/cucumber/core/backend/Glue.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.cucumber.core.backend;
22

33
import io.cucumber.core.stepexpression.TypeRegistry;
4+
import io.cucumber.datatable.DataTableType;
45
import org.apiguardian.api.API;
56

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

2122
void addParameterType(ParameterTypeDefinition parameterTypeDefinition);
2223

24+
void addDataTableType(DataTableTypeTypeDefinition dataTableTypeTypeDefinition);
25+
2326
}

core/src/main/java/io/cucumber/core/runner/CachingGlue.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.cucumber.core.runner;
22

3+
import io.cucumber.core.backend.DataTableTypeTypeDefinition;
34
import io.cucumber.core.backend.ParameterTypeDefinition;
45
import io.cucumber.core.event.StepDefinedEvent;
56
import io.cucumber.core.backend.DuplicateStepDefinitionException;
@@ -86,6 +87,11 @@ public void addParameterType(ParameterTypeDefinition parameterTypeDefinition) {
8687
typeRegistry.defineParameterType(parameterTypeDefinition.parameterType());
8788
}
8889

90+
@Override
91+
public void addDataTableType(DataTableTypeTypeDefinition dataTableTypeTypeDefinition) {
92+
typeRegistry.defineDataTableType(dataTableTypeTypeDefinition.dataTableType());
93+
}
94+
8995
List<HookDefinition> getBeforeHooks() {
9096
return new ArrayList<>(beforeHooks);
9197
}
@@ -131,7 +137,7 @@ PickleStepDefinitionMatch stepDefinitionMatch(String featurePath, PickleStep ste
131137
}
132138

133139
private List<PickleStepDefinitionMatch> stepDefinitionMatches(String featurePath, PickleStep step) {
134-
List<PickleStepDefinitionMatch> result = new ArrayList<PickleStepDefinitionMatch>();
140+
List<PickleStepDefinitionMatch> result = new ArrayList<>();
135141
for (StepDefinition stepDefinition : stepDefinitionsByPattern.values()) {
136142
List<Argument> arguments = stepDefinition.matchedArguments(step);
137143
if (arguments != null) {

core/src/main/java/io/cucumber/core/runner/Runner.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package io.cucumber.core.runner;
22

3-
import io.cucumber.core.event.HookType;
4-
import io.cucumber.core.event.SnippetsSuggestedEvent;
53
import gherkin.events.PickleEvent;
64
import gherkin.pickles.PickleStep;
75
import gherkin.pickles.PickleTag;
86
import io.cucumber.core.backend.Backend;
97
import io.cucumber.core.backend.HookDefinition;
108
import io.cucumber.core.backend.ObjectFactory;
9+
import io.cucumber.core.event.HookType;
10+
import io.cucumber.core.event.SnippetsSuggestedEvent;
1111
import io.cucumber.core.eventbus.EventBus;
1212
import io.cucumber.core.logging.Logger;
1313
import io.cucumber.core.logging.LoggerFactory;
@@ -57,10 +57,13 @@ public EventBus getBus() {
5757
}
5858

5959
public void runPickle(PickleEvent pickle) {
60-
buildBackendWorlds(); // Java8 step definitions will be added to the glue here
61-
TestCase testCase = createTestCaseForPickle(pickle);
62-
testCase.run(bus);
63-
disposeBackendWorlds();
60+
try {
61+
buildBackendWorlds(); // Java8 step definitions will be added to the glue here
62+
TestCase testCase = createTestCaseForPickle(pickle);
63+
testCase.run(bus);
64+
} finally {
65+
disposeBackendWorlds();
66+
}
6467
}
6568

6669
private TestCase createTestCaseForPickle(PickleEvent pickleEvent) {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package io.cucumber.java;
2+
3+
import org.apiguardian.api.API;
4+
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
/**
11+
* Allows a DataTableType to be registered.
12+
*
13+
* Supports TableCellTransformer: String -> T
14+
* Supports TableEntryTransformer: Map<String, String> -> T
15+
* Supports TableRowTransformer: List<String> -> T
16+
* Supports TableTransformer: DataTable -> T
17+
*/
18+
@Retention(RetentionPolicy.RUNTIME)
19+
@Target(ElementType.METHOD)
20+
@API(status = API.Status.STABLE)
21+
public @interface DataTableType {
22+
23+
}

java/src/main/java/io/cucumber/java/JavaBackend.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ void addHook(Annotation annotation, Method method) {
9595
boolean useForSnippets = parameterType.useForSnippets();
9696
boolean preferForRegexMatch = parameterType.preferForRegexMatch();
9797
glue.addParameterType(new JavaParameterTypeDefinition(name, pattern, method, useForSnippets, preferForRegexMatch, lookup));
98+
} else if (annotation.annotationType().equals(DataTableType.class)) {
99+
glue.addDataTableType(new JavaDataTableTypeDefinition(method, lookup));
98100
}
99101
}
100102
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package io.cucumber.java;
2+
3+
import io.cucumber.core.backend.DataTableTypeTypeDefinition;
4+
import io.cucumber.core.backend.Lookup;
5+
import io.cucumber.core.exception.CucumberException;
6+
import io.cucumber.core.runtime.Invoker;
7+
import io.cucumber.datatable.DataTable;
8+
import io.cucumber.datatable.DataTableType;
9+
import io.cucumber.datatable.TableCellTransformer;
10+
import io.cucumber.datatable.TableEntryTransformer;
11+
import io.cucumber.datatable.TableRowTransformer;
12+
import io.cucumber.datatable.TableTransformer;
13+
14+
import java.lang.reflect.Method;
15+
import java.lang.reflect.ParameterizedType;
16+
import java.lang.reflect.Type;
17+
import java.util.List;
18+
import java.util.Map;
19+
20+
class JavaDataTableTypeDefinition implements DataTableTypeTypeDefinition {
21+
22+
private final Method method;
23+
private final Lookup lookup;
24+
private final DataTableType dataTableType;
25+
26+
JavaDataTableTypeDefinition(Method method, Lookup lookup) {
27+
this.method = method;
28+
this.lookup = lookup;
29+
this.dataTableType = createDataTableType(method);
30+
}
31+
32+
@SuppressWarnings("unchecked")
33+
private DataTableType createDataTableType(Method method) {
34+
Class returnType = requireValidReturnType(method);
35+
Type parameterType = requireValidParameterType(method);
36+
37+
if (DataTable.class.equals(parameterType)) {
38+
return new DataTableType(
39+
returnType,
40+
(TableTransformer<Object>) this::execute
41+
);
42+
}
43+
44+
if (List.class.equals(parameterType)) {
45+
return new DataTableType(
46+
returnType,
47+
(TableRowTransformer<Object>) this::execute
48+
);
49+
}
50+
51+
if (Map.class.equals(parameterType)) {
52+
return new DataTableType(
53+
returnType,
54+
(TableEntryTransformer<Object>) this::execute
55+
);
56+
}
57+
58+
if (String.class.equals(parameterType)) {
59+
return new DataTableType(
60+
returnType,
61+
(TableCellTransformer<Object>) this::execute
62+
);
63+
}
64+
65+
throw createInvalidSignatureException();
66+
67+
}
68+
69+
private static CucumberException createInvalidSignatureException() {
70+
return new CucumberException("" +
71+
"A @DataTableType annotated method must have one of these signatures:\n" +
72+
" * public Author author(DataTable table)\n" +
73+
" * public Author author(List<String> row)\n" +
74+
" * public Author author(Map<String, String> entry)\n" +
75+
" * public Author author(String cell)\n" +
76+
"Note: Author is an example of the class you want to convert the table to"
77+
);
78+
}
79+
80+
81+
private static Type requireValidParameterType(Method method) {
82+
Type[] parameterTypes = method.getGenericParameterTypes();
83+
if (parameterTypes.length != 1) {
84+
throw createInvalidSignatureException();
85+
}
86+
87+
Type parameterType = parameterTypes[0];
88+
if (!(parameterType instanceof ParameterizedType)) {
89+
return parameterType;
90+
}
91+
92+
ParameterizedType parameterizedType = (ParameterizedType) parameterType;
93+
Type[] typeParameters = parameterizedType.getActualTypeArguments();
94+
for (Type typeParameter : typeParameters) {
95+
if (!String.class.equals(typeParameter)) {
96+
throw createInvalidSignatureException();
97+
}
98+
}
99+
100+
return parameterizedType.getRawType();
101+
}
102+
103+
private static Class requireValidReturnType(Method method) {
104+
Class returnType = method.getReturnType();
105+
if (Void.class.equals(returnType)) {
106+
throw createInvalidSignatureException();
107+
}
108+
return returnType;
109+
}
110+
111+
112+
@Override
113+
public DataTableType dataTableType() {
114+
return dataTableType;
115+
}
116+
117+
118+
private Object execute(Object arg) throws Throwable {
119+
return Invoker.invoke(lookup.getInstance(method.getDeclaringClass()), method, 0, arg);
120+
}
121+
122+
}

java/src/main/java/io/cucumber/java/JavaParameterTypeDefinition.java

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,58 +8,60 @@
88

99
import java.lang.reflect.Method;
1010
import java.util.Collections;
11-
import java.util.List;
1211

13-
public class JavaParameterTypeDefinition implements ParameterTypeDefinition {
12+
class JavaParameterTypeDefinition implements ParameterTypeDefinition {
1413

15-
private final String name;
16-
private final List<String> patterns;
1714
private final Method method;
1815

1916
private final Lookup lookup;
20-
private final boolean preferForRegexpMatch;
21-
private final boolean useForSnippets;
17+
private final ParameterType<Object> parameterType;
2218

2319
JavaParameterTypeDefinition(String name, String pattern, Method method, boolean useForSnippets, boolean preferForRegexpMatch, Lookup lookup) {
24-
this.name = name.isEmpty() ? method.getName() : name;
25-
this.patterns = Collections.singletonList(pattern);
2620
this.method = requireValidMethod(method);
2721
this.lookup = lookup;
28-
this.useForSnippets = useForSnippets;
29-
this.preferForRegexpMatch = preferForRegexpMatch;
22+
this.parameterType = new ParameterType<>(
23+
name.isEmpty() ? method.getName() : name,
24+
Collections.singletonList(pattern),
25+
this.method.getReturnType(),
26+
this::execute,
27+
useForSnippets,
28+
preferForRegexpMatch
29+
);
3030
}
3131

3232
private Method requireValidMethod(Method method) {
3333
Class<?> returnType = method.getReturnType();
3434
if (Void.class.equals(returnType)) {
35-
throw new CucumberException("TODO");
35+
throw createInvalidSignatureException();
3636
}
3737

3838
Class<?>[] parameterTypes = method.getParameterTypes();
3939
if (parameterTypes.length < 1) {
40-
throw new CucumberException("TODO");
40+
throw createInvalidSignatureException();
4141
}
4242

43-
for (int i = 0; i < parameterTypes.length; i++) {
44-
Class<?> parameterType = parameterTypes[i];
43+
for (Class<?> parameterType : parameterTypes) {
4544
if (!String.class.equals(parameterType)) {
46-
throw new CucumberException("TODO" + i);
45+
throw createInvalidSignatureException();
4746
}
4847
}
4948

5049
return method;
5150
}
5251

52+
private CucumberException createInvalidSignatureException() {
53+
return new CucumberException("" +
54+
"A @ParameterType annotated method must have one of these signatures:\n" +
55+
" * public Author parameterName(String all)\n" +
56+
" * public Author parameterName(String captureGroup1, String captureGroup2, ...ect )\n" +
57+
" * public Author parameterName(String... captureGroups)\n" +
58+
"Note: Author is an example of the class you want to convert parameter name"
59+
);
60+
}
61+
5362
@Override
5463
public ParameterType<?> parameterType() {
55-
return new ParameterType<>(
56-
name,
57-
patterns,
58-
method.getReturnType(),
59-
this::execute,
60-
useForSnippets,
61-
preferForRegexpMatch
62-
);
64+
return parameterType;
6365
}
6466

6567
private Object execute(Object[] args) throws Throwable {

java/src/main/java/io/cucumber/java/MethodScanner.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ private boolean isHookAnnotation(Annotation annotation) {
8484
|| annotationClass.equals(BeforeStep.class)
8585
|| annotationClass.equals(AfterStep.class)
8686
|| annotationClass.equals(ParameterType.class)
87+
|| annotationClass.equals(DataTableType.class)
8788
;
8889
}
8990

java/src/main/java/io/cucumber/java/ParameterType.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
import java.lang.annotation.RetentionPolicy;
88
import java.lang.annotation.Target;
99

10+
/**
11+
* Defines a parameter type.
12+
*
13+
* Method signature must have a String argument for each capture group
14+
*
15+
*/
1016
@Retention(RetentionPolicy.RUNTIME)
1117
@Target(ElementType.METHOD)
1218
@API(status = API.Status.STABLE)

0 commit comments

Comments
 (0)