diff --git a/clojure/src/main/java/cucumber/runtime/clojure/ClojureStepDefinition.java b/clojure/src/main/java/cucumber/runtime/clojure/ClojureStepDefinition.java index 3cc66885ee..bfcfa77e03 100644 --- a/clojure/src/main/java/cucumber/runtime/clojure/ClojureStepDefinition.java +++ b/clojure/src/main/java/cucumber/runtime/clojure/ClojureStepDefinition.java @@ -35,6 +35,11 @@ public List matchedArguments(Step step) { return new JdkPatternArgumentMatcher(pattern).argumentsFrom(step.getName()); } + @Override + public Class getTypeForTableList(int argIndex) { + return null; + } + public String getLocation() { return location.getFileName() + ":" + location.getLineNumber(); } @@ -60,9 +65,4 @@ public boolean isDefinedAt(StackTraceElement stackTraceElement) { public String getPattern() { return pattern.pattern(); } - - @Override - public Object tableArgument(int argIndex, List rows, TableConverter tableConverter) { - return new Table(rows); - } } diff --git a/core/src/main/java/cucumber/runtime/Runtime.java b/core/src/main/java/cucumber/runtime/Runtime.java index 403c2dbaf3..64cc9dcd7c 100644 --- a/core/src/main/java/cucumber/runtime/Runtime.java +++ b/core/src/main/java/cucumber/runtime/Runtime.java @@ -2,6 +2,8 @@ import cucumber.resources.Resources; import cucumber.runtime.converters.LocalizedXStreams; +import cucumber.table.CamelCaseHeaderMapper; +import cucumber.table.TableHeaderMapper; import gherkin.formatter.Argument; import gherkin.formatter.model.Step; @@ -13,6 +15,7 @@ public class Runtime { private final List undefinedSteps = new ArrayList(); private final List backends; private final LocalizedXStreams localizedXStreams = new LocalizedXStreams(); + private final TableHeaderMapper tableHeaderMapper = new CamelCaseHeaderMapper(); public Runtime(Backend... backends) { this.backends = asList(backends); @@ -41,7 +44,7 @@ private List stepDefinitionMatches(String uri, Step step) { for (StepDefinition stepDefinition : backend.getStepDefinitions()) { List arguments = stepDefinition.matchedArguments(step); if (arguments != null) { - result.add(new StepDefinitionMatch(arguments, stepDefinition, uri, step, localizedXStreams)); + result.add(new StepDefinitionMatch(arguments, stepDefinition, uri, step, localizedXStreams, tableHeaderMapper)); } } } diff --git a/core/src/main/java/cucumber/runtime/StepDefinition.java b/core/src/main/java/cucumber/runtime/StepDefinition.java index 2cb8a16289..9d1219dde3 100644 --- a/core/src/main/java/cucumber/runtime/StepDefinition.java +++ b/core/src/main/java/cucumber/runtime/StepDefinition.java @@ -1,8 +1,6 @@ package cucumber.runtime; -import cucumber.table.TableConverter; import gherkin.formatter.Argument; -import gherkin.formatter.model.Row; import gherkin.formatter.model.Step; import java.util.List; @@ -16,18 +14,14 @@ public interface StepDefinition { List matchedArguments(Step step); /** - * Returns a different representation of {@code table} for the argument at position {@code argIndex}. This allows - * step definitions to accept a higher level argument type. If the implementation does not know how to transform - * a table, the table itself should be returned. - * - * - * @param argIndex index of the argument - * @param rows - * @param tableConverter - * @return the {@link Object} associated with the argument - * at argIndex or table if there's none + * Cucumber will try to convert each Gherkin step table into a {@link List} of objects. The header row is used to + * identify property names of the objects, and each row underneath will be converted to an object. + * + * If this method returns null, Cucumber will convert the rows into an instance of {@link cucumber.table.Table}. + * + * @return the kind of object Cucumber should instantiate for each row, or null if no conversion should happen. */ - Object tableArgument(int argIndex, List rows, TableConverter tableConverter); + Class getTypeForTableList(int argIndex); /** * The source line where the step definition is defined. diff --git a/core/src/main/java/cucumber/runtime/StepDefinitionMatch.java b/core/src/main/java/cucumber/runtime/StepDefinitionMatch.java index 0ecffcba60..73503562e6 100644 --- a/core/src/main/java/cucumber/runtime/StepDefinitionMatch.java +++ b/core/src/main/java/cucumber/runtime/StepDefinitionMatch.java @@ -3,12 +3,16 @@ import com.thoughtworks.xstream.converters.ConverterLookup; import com.thoughtworks.xstream.converters.SingleValueConverter; import cucumber.runtime.converters.LocalizedXStreams; +import cucumber.table.Table; import cucumber.table.TableConverter; +import cucumber.table.TableHeaderMapper; import gherkin.formatter.Argument; import gherkin.formatter.model.Match; +import gherkin.formatter.model.Row; import gherkin.formatter.model.Step; import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -19,13 +23,15 @@ public class StepDefinitionMatch extends Match { private final String uri; private final Step step; private final LocalizedXStreams localizedXStreams; + private final TableHeaderMapper tableHeaderMapper; - public StepDefinitionMatch(List arguments, StepDefinition stepDefinition, String uri, Step step, LocalizedXStreams localizedXStreams) { + public StepDefinitionMatch(List arguments, StepDefinition stepDefinition, String uri, Step step, LocalizedXStreams localizedXStreams, TableHeaderMapper tableHeaderMapper) { super(arguments, stepDefinition.getLocation()); this.stepDefinition = stepDefinition; this.uri = uri; this.step = step; this.localizedXStreams = localizedXStreams; + this.tableHeaderMapper = tableHeaderMapper; } public void runStep(Locale locale) throws Throwable { @@ -33,7 +39,8 @@ public void runStep(Locale locale) throws Throwable { throw new NullPointerException("null Locale!"); } try { - stepDefinition.execute(transformedArgs(stepDefinition.getParameterTypes(), step, locale)); + Object[] args = transformedArgs(stepDefinition.getParameterTypes(), step, locale); + stepDefinition.execute(args); } catch (CucumberException e) { throw e; } catch (InvocationTargetException t) { @@ -70,9 +77,39 @@ private Object[] transformedArgs(Class[] parameterTypes, Step step, Locale lo } private Object tableArgument(Step step, TableConverter tableConverter, int argIndex) { - return stepDefinition.tableArgument(argIndex, step.getRows(), tableConverter); + Class listType = stepDefinition.getTypeForTableList(argIndex); + if(listType != null) { + return tableConverter.convert(listType, attributeNames(step.getRows()), attributeValues(step.getRows())); + } else { + return new Table(step.getRows()); + } + } + + private List> attributeValues(List rows) { + List> attributeValues = new ArrayList>(); + List valueRows = rows.subList(1, rows.size()); + for (Row valueRow : valueRows) { + attributeValues.add(toStrings(valueRow)); + } + return attributeValues; } + private List attributeNames(List rows) { + List strings = new ArrayList(); + for (String string : rows.get(0).getCells()) { + strings.add(tableHeaderMapper.map(string)); + } + return strings; + } + + private List toStrings(Row row) { + List strings = new ArrayList(); + for (String string : row.getCells()) { + strings.add(string); + } + return strings; + } + public Throwable filterStacktrace(Throwable error, StackTraceElement stepLocation) { StackTraceElement[] stackTraceElements = error.getStackTrace(); if (error.getCause() != null && error.getCause() != error) { diff --git a/java/src/main/java/cucumber/table/java/JavaBeanPropertyHeaderMapper.java b/core/src/main/java/cucumber/table/CamelCaseHeaderMapper.java similarity index 83% rename from java/src/main/java/cucumber/table/java/JavaBeanPropertyHeaderMapper.java rename to core/src/main/java/cucumber/table/CamelCaseHeaderMapper.java index 7e49c48341..23232d6e1a 100644 --- a/java/src/main/java/cucumber/table/java/JavaBeanPropertyHeaderMapper.java +++ b/core/src/main/java/cucumber/table/CamelCaseHeaderMapper.java @@ -1,10 +1,8 @@ -package cucumber.table.java; - -import cucumber.table.TableHeaderMapper; +package cucumber.table; import java.util.regex.Pattern; -public class JavaBeanPropertyHeaderMapper implements TableHeaderMapper { +public class CamelCaseHeaderMapper implements TableHeaderMapper { private static final String WHITESPACE = " "; private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); @@ -21,8 +19,8 @@ public String map(String originalHeaderName) { private String join(String[] splitted) { StringBuilder sb = new StringBuilder(); - for (int i = 0; i < splitted.length; i++) { - sb.append(splitted[i]); + for (String s : splitted) { + sb.append(s); } return sb.toString(); } diff --git a/core/src/test/java/cucumber/runtime/StepDefinitionMatchTest.java b/core/src/test/java/cucumber/runtime/StepDefinitionMatchTest.java index 542864f4e7..145d9c6af6 100644 --- a/core/src/test/java/cucumber/runtime/StepDefinitionMatchTest.java +++ b/core/src/test/java/cucumber/runtime/StepDefinitionMatchTest.java @@ -23,7 +23,7 @@ public void shouldConvertParameters() throws Throwable { when(stepWithoutDocStringOrTable.getDocString()).thenReturn(null); when(stepWithoutDocStringOrTable.getRows()).thenReturn(null); - StepDefinitionMatch stepDefinitionMatch = new StepDefinitionMatch(arguments, stepDefinition, "some.feature", stepWithoutDocStringOrTable, new LocalizedXStreams()); + StepDefinitionMatch stepDefinitionMatch = new StepDefinitionMatch(arguments, stepDefinition, "some.feature", stepWithoutDocStringOrTable, new LocalizedXStreams(), null); stepDefinitionMatch.runStep(Locale.ENGLISH); Object[] args = {5}; verify(stepDefinition).execute(args); diff --git a/java/src/test/java/cucumber/table/java/JavaBeanPropertyHeaderMapperTest.java b/core/src/test/java/cucumber/table/CamelCaseHeaderMapperTest.java similarity index 70% rename from java/src/test/java/cucumber/table/java/JavaBeanPropertyHeaderMapperTest.java rename to core/src/test/java/cucumber/table/CamelCaseHeaderMapperTest.java index a58b74629d..41ce682c63 100644 --- a/java/src/test/java/cucumber/table/java/JavaBeanPropertyHeaderMapperTest.java +++ b/core/src/test/java/cucumber/table/CamelCaseHeaderMapperTest.java @@ -1,13 +1,13 @@ -package cucumber.table.java; +package cucumber.table; import org.junit.Test; import static org.junit.Assert.assertEquals; -public class JavaBeanPropertyHeaderMapperTest { +public class CamelCaseHeaderMapperTest { @Test public void testTransformToJavaPropertyName() { - JavaBeanPropertyHeaderMapper mapper = new JavaBeanPropertyHeaderMapper(); + CamelCaseHeaderMapper mapper = new CamelCaseHeaderMapper(); assertEquals("Transformed Name", "userName", mapper.map("User Name")); assertEquals("Transformed Name", "birthDate", mapper.map(" Birth Date\t")); assertEquals("Transformed Name", "email", mapper.map("email")); diff --git a/groovy/src/main/java/cucumber/runtime/groovy/GroovyStepDefinition.java b/groovy/src/main/java/cucumber/runtime/groovy/GroovyStepDefinition.java index 5e037ddf6d..26d76a6d51 100644 --- a/groovy/src/main/java/cucumber/runtime/groovy/GroovyStepDefinition.java +++ b/groovy/src/main/java/cucumber/runtime/groovy/GroovyStepDefinition.java @@ -9,6 +9,8 @@ import gherkin.formatter.model.Step; import groovy.lang.Closure; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.List; import java.util.regex.Pattern; @@ -31,6 +33,11 @@ public List matchedArguments(Step step) { return argumentMatcher.argumentsFrom(step.getName()); } + @Override + public Class getTypeForTableList(int argIndex) { + return null; + } + public String getLocation() { return location.getFileName() + ":" + location.getLineNumber(); } @@ -51,9 +58,4 @@ public boolean isDefinedAt(StackTraceElement stackTraceElement) { public String getPattern() { return pattern.pattern(); } - - @Override - public Object tableArgument(int argIndex, List rows, TableConverter tableConverter) { - return new Table(rows); - } } diff --git a/ioke/src/main/java/cucumber/runtime/ioke/IokeStepDefinition.java b/ioke/src/main/java/cucumber/runtime/ioke/IokeStepDefinition.java index 41b87238d4..b9968bda95 100644 --- a/ioke/src/main/java/cucumber/runtime/ioke/IokeStepDefinition.java +++ b/ioke/src/main/java/cucumber/runtime/ioke/IokeStepDefinition.java @@ -2,10 +2,7 @@ import cucumber.runtime.CucumberException; import cucumber.runtime.StepDefinition; -import cucumber.table.Table; -import cucumber.table.TableConverter; import gherkin.formatter.Argument; -import gherkin.formatter.model.Row; import gherkin.formatter.model.Step; import ioke.lang.IokeObject; import ioke.lang.Runtime; @@ -47,6 +44,11 @@ public List matchedArguments(Step step) { } } + @Override + public Class getTypeForTableList(int argIndex) { + return null; + } + public String getLocation() { return location; } @@ -74,9 +76,4 @@ public void execute(Object[] args) throws Throwable { public boolean isDefinedAt(StackTraceElement stackTraceElement) { return stackTraceElement.getClassName().equals(IokeBackend.class.getName()); } - - @Override - public Object tableArgument(int argIndex, List rows, TableConverter tableConverter) { - return new Table(rows); - } } diff --git a/java/src/main/java/cucumber/runtime/java/JavaStepDefinition.java b/java/src/main/java/cucumber/runtime/java/JavaStepDefinition.java index 5581248df2..76a54666b5 100644 --- a/java/src/main/java/cucumber/runtime/java/JavaStepDefinition.java +++ b/java/src/main/java/cucumber/runtime/java/JavaStepDefinition.java @@ -3,17 +3,12 @@ import cucumber.runtime.CucumberException; import cucumber.runtime.JdkPatternArgumentMatcher; import cucumber.runtime.StepDefinition; -import cucumber.table.Table; -import cucumber.table.TableConverter; -import cucumber.table.java.JavaBeanPropertyHeaderMapper; import gherkin.formatter.Argument; -import gherkin.formatter.model.Row; import gherkin.formatter.model.Step; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; @@ -25,7 +20,6 @@ public class JavaStepDefinition implements StepDefinition { private final ObjectFactory objectFactory; private final JdkPatternArgumentMatcher argumentMatcher; private final Pattern pattern; - private final JavaBeanPropertyHeaderMapper mapper = new JavaBeanPropertyHeaderMapper(); public JavaStepDefinition(Pattern pattern, Method method, ObjectFactory objectFactory) { this.pattern = pattern; @@ -67,39 +61,13 @@ public String getPattern() { } @Override - public Object tableArgument(int argIndex, List rows, TableConverter tableConverter) { + public Class getTypeForTableList(int argIndex) { Type genericParameterType = method.getGenericParameterTypes()[argIndex]; if (genericParameterType instanceof ParameterizedType) { Type[] parameters = ((ParameterizedType) genericParameterType).getActualTypeArguments(); - Class itemType = (Class) parameters[0]; - return tableConverter.convert(itemType, attributeNames(rows), attributeValues(rows)); + return (Class) parameters[0]; } else { - return new Table(rows); + return null; } } - - private List> attributeValues(List rows) { - List> attributeValues = new ArrayList>(); - List valueRows = rows.subList(1, rows.size()); - for (Row valueRow : valueRows) { - attributeValues.add(toStrings(valueRow)); - } - return attributeValues; - } - - private List attributeNames(List rows) { - List strings = new ArrayList(); - for (String string : rows.get(0).getCells()) { - strings.add(mapper.map(string)); - } - return strings; - } - - private List toStrings(Row row) { - List strings = new ArrayList(); - for (String string : row.getCells()) { - strings.add(string); - } - return strings; - } } diff --git a/java/src/test/java/cucumber/runtime/java/JavaBeanTableProcessorTest.java b/java/src/test/java/cucumber/runtime/java/JavaBeanTableProcessorTest.java index 4a8d2f44ef..e1f592740b 100644 --- a/java/src/test/java/cucumber/runtime/java/JavaBeanTableProcessorTest.java +++ b/java/src/test/java/cucumber/runtime/java/JavaBeanTableProcessorTest.java @@ -3,6 +3,7 @@ import cucumber.runtime.StepDefinition; import cucumber.runtime.StepDefinitionMatch; import cucumber.runtime.converters.LocalizedXStreams; +import cucumber.table.CamelCaseHeaderMapper; import cucumber.table.java.User; import gherkin.formatter.Argument; import gherkin.formatter.model.Comment; @@ -39,7 +40,7 @@ public void shouldExecuteWithAListOfUsers() throws Throwable { Step stepWithRows = new Step(NO_COMMENTS, "Given", "something that wants users", 10); stepWithRows.setMultilineArg(rowsList()); - StepDefinitionMatch stepDefinitionMatch = new StepDefinitionMatch(NO_ARGS, stepDefinition, "some.feature", stepWithRows, new LocalizedXStreams()); + StepDefinitionMatch stepDefinitionMatch = new StepDefinitionMatch(NO_ARGS, stepDefinition, "some.feature", stepWithRows, new LocalizedXStreams(), new CamelCaseHeaderMapper()); stepDefinitionMatch.runStep(Locale.UK); assertEquals(asList(new User("Sid Vicious", sidsBirthday(), 1000)), stepDefs.users); diff --git a/jruby/src/main/java/cucumber/runtime/jruby/JRubyStepDefinition.java b/jruby/src/main/java/cucumber/runtime/jruby/JRubyStepDefinition.java index 98cbb73ed3..662524ddc5 100644 --- a/jruby/src/main/java/cucumber/runtime/jruby/JRubyStepDefinition.java +++ b/jruby/src/main/java/cucumber/runtime/jruby/JRubyStepDefinition.java @@ -2,10 +2,7 @@ import cucumber.runtime.StepDefinition; import cucumber.runtime.Utils; -import cucumber.table.Table; -import cucumber.table.TableConverter; import gherkin.formatter.Argument; -import gherkin.formatter.model.Row; import gherkin.formatter.model.Step; import org.jruby.RubyObject; import org.jruby.RubyString; @@ -30,6 +27,11 @@ public List matchedArguments(Step step) { return (List) arguments.toJava(List.class); } + @Override + public Class getTypeForTableList(int argIndex) { + return null; + } + @Override public String getLocation() { if (file == null) { @@ -66,9 +68,4 @@ public boolean isDefinedAt(StackTraceElement stackTraceElement) { public String getPattern() { return (String) stepdef.callMethod("pattern").toJava(String.class); } - - @Override - public Object tableArgument(int argIndex, List rows, TableConverter tableConverter) { - return new Table(rows); - } } diff --git a/rhino/src/main/java/cucumber/runtime/rhino/RhinoStepDefinition.java b/rhino/src/main/java/cucumber/runtime/rhino/RhinoStepDefinition.java index 71b5f3a5cd..7271411728 100644 --- a/rhino/src/main/java/cucumber/runtime/rhino/RhinoStepDefinition.java +++ b/rhino/src/main/java/cucumber/runtime/rhino/RhinoStepDefinition.java @@ -39,6 +39,11 @@ public List matchedArguments(Step step) { return args == null ? null : (List) args.unwrap(); } + @Override + public Class getTypeForTableList(int argIndex) { + return null; + } + public String getLocation() { return location.getFileName() + ":" + location.getLineNumber(); } @@ -63,9 +68,4 @@ public boolean isDefinedAt(StackTraceElement stackTraceElement) { public String getPattern() { return regexp.toString(); } - - @Override - public Object tableArgument(int argIndex, List rows, TableConverter tableConverter) { - return new Table(rows); - } } diff --git a/scala/src/main/scala/cucumber/runtime/ScalaStepDefinition.scala b/scala/src/main/scala/cucumber/runtime/ScalaStepDefinition.scala index b1aaa64cc8..c5dac18d2d 100644 --- a/scala/src/main/scala/cucumber/runtime/ScalaStepDefinition.scala +++ b/scala/src/main/scala/cucumber/runtime/ScalaStepDefinition.scala @@ -17,7 +17,7 @@ class ScalaStepDefinition(frame:StackTraceElement, name:String, pattern:String, def matchedArguments(step: Step) = argumentMatcher.argumentsFrom(step.getName) - def tableArgument(argIndex: Int, rows: java.util.List[Row], locale: Locale) = new Table(rows, locale) + def getTypeForTableList(argIndex: Int) = null def getLocation = frame.getFileName + ":" + frame.getLineNumber