diff --git a/distribution/src/site/content/tabular-parameters.html b/distribution/src/site/content/tabular-parameters.html
index f48511266..50d26af3d 100755
--- a/distribution/src/site/content/tabular-parameters.html
+++ b/distribution/src/site/content/tabular-parameters.html
@@ -191,6 +191,53 @@
Mapping parameters to custom types
}
+Convert row to type using custom converter
+
+It may sometime be useful to map the whole row to a custom object using custom converter rather than relying on field to field mapping
+
+Given the following class that we want to convert from Parameter
+
+
+
+Create parameter converter for the Person class, please do not forget to register converter in ParameterConverters
+
+
+
+Use as(java.lang.reflect.Type) method to perform the conversion
+
+
+
Preserving whitespace
By default, value in the table are trimmed, i.e. any preceding
diff --git a/jbehave-core/src/main/java/org/jbehave/core/steps/ConvertedParameters.java b/jbehave-core/src/main/java/org/jbehave/core/steps/ConvertedParameters.java
index 735b9b2c5..8b9877586 100755
--- a/jbehave-core/src/main/java/org/jbehave/core/steps/ConvertedParameters.java
+++ b/jbehave-core/src/main/java/org/jbehave/core/steps/ConvertedParameters.java
@@ -45,6 +45,12 @@ public ConvertedParameters(Map values, ParameterConverters param
this.parameterConverters = parameterConverters;
}
+ @Override
+ @SuppressWarnings("unchecked")
+ public T as(Type type) {
+ return (T) parameterConverters.convert(this, Parameters.class, type);
+ }
+
@Override
public T valueAs(String name, Type type) {
return convert(valueFor(name), type);
diff --git a/jbehave-core/src/main/java/org/jbehave/core/steps/ParameterConverters.java b/jbehave-core/src/main/java/org/jbehave/core/steps/ParameterConverters.java
index 10e09e5da..ef96a08fb 100755
--- a/jbehave-core/src/main/java/org/jbehave/core/steps/ParameterConverters.java
+++ b/jbehave-core/src/main/java/org/jbehave/core/steps/ParameterConverters.java
@@ -59,6 +59,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -344,8 +345,9 @@ public ParameterConverters addConverters(List extends ParameterConverter> conv
return this;
}
- private static boolean isChainComplete(Queue convertersChain) {
- return !convertersChain.isEmpty() && isBaseType(convertersChain.peek().getSourceType());
+ private static boolean isChainComplete(Queue convertersChain,
+ Predicate sourceTypePredicate) {
+ return !convertersChain.isEmpty() && sourceTypePredicate.test(convertersChain.peek().getSourceType());
}
private static Object applyConverters(Object value, Type basicType, Queue convertersChain) {
@@ -354,20 +356,25 @@ private static Object applyConverters(Object value, Type basicType, Queue c.convertValue(v, c.getTargetType()), (l, r) -> l);
}
- @SuppressWarnings({ "unchecked", "rawtypes" })
public Object convert(String value, Type type) {
- Queue converters = findConverters(type);
- if (isChainComplete(converters)) {
+ return convert(value, String.class, type);
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public Object convert(Object value, Class> source, Type type) {
+ Predicate sourceTypePredicate = t -> source.isAssignableFrom((Class>) t);
+ Queue converters = findConverters(type, sourceTypePredicate);
+ if (isChainComplete(converters, sourceTypePredicate)) {
Object converted = applyConverters(value, type, converters);
Queue> classes = converters.stream().map(ParameterConverter::getClass)
.collect(Collectors.toCollection(LinkedList::new));
- monitor.convertedValueOfType(value, type, converted, classes);
+ monitor.convertedValueOfType(String.valueOf(value), type, converted, classes);
return converted;
}
if (isAssignableFromRawType(Optional.class, type)) {
Type elementType = argumentType(type);
- return Optional.of(convert(value, elementType));
+ return Optional.of(convert(value, source, elementType));
}
if (isAssignableFromRawType(Collection.class, type)) {
@@ -375,13 +382,14 @@ public Object convert(String value, Type type) {
Collection collection = createCollection(rawClass(type));
if (collection != null) {
- Queue typeConverters = findConverters(elementType);
+ Queue typeConverters = findConverters(elementType, sourceTypePredicate);
if (!typeConverters.isEmpty()) {
Type sourceType = typeConverters.peek().getSourceType();
- if (isBaseType(sourceType)) {
- fillCollection(value, escapedCollectionSeparator, typeConverters, elementType, collection);
+ if (String.class.isAssignableFrom((Class>) sourceType)) {
+ fillCollection((String) value, escapedCollectionSeparator, typeConverters, elementType,
+ collection);
} else if (isAssignableFrom(Parameters.class, sourceType)) {
ExamplesTable table = (ExamplesTable) findBaseConverter(ExamplesTable.class).convertValue(value,
String.class);
@@ -396,7 +404,7 @@ public Object convert(String value, Type type) {
if (type instanceof Class) {
Class clazz = (Class) type;
if (clazz.isArray()) {
- String[] elements = parseElements(value, escapedCollectionSeparator);
+ String[] elements = parseElements((String) value, escapedCollectionSeparator);
Class elementType = clazz.getComponentType();
ParameterConverter elementConverter = findBaseConverter(elementType);
Object array = createArray(elementType, elements.length);
@@ -420,29 +428,26 @@ private ParameterConverter findBaseConverter(Type type) {
return null;
}
- private Queue findConverters(Type type) {
+ private Queue findConverters(Type type, Predicate sourceTypePredicate) {
LinkedList convertersChain = new LinkedList<>();
- putConverters(type, convertersChain);
+ putConverters(type, convertersChain, sourceTypePredicate);
return convertersChain;
}
- private void putConverters(Type type, LinkedList container) {
+ private void putConverters(Type type, LinkedList container,
+ Predicate sourceTypePredicate) {
for (ParameterConverter converter : converters) {
if (converter.canConvertTo(type)) {
container.addFirst(converter);
Type sourceType = converter.getSourceType();
- if (isBaseType(sourceType)) {
+ if (sourceTypePredicate.test(sourceType)) {
break;
}
- putConverters(sourceType, container);
+ putConverters(sourceType, container, sourceTypePredicate);
}
}
}
- private static boolean isBaseType(Type type) {
- return String.class.isAssignableFrom((Class>) type);
- }
-
private static boolean isAssignableFrom(Class> clazz, Type type) {
return type instanceof Class> && clazz.isAssignableFrom((Class>) type);
}
diff --git a/jbehave-core/src/main/java/org/jbehave/core/steps/Parameters.java b/jbehave-core/src/main/java/org/jbehave/core/steps/Parameters.java
index af4f1daef..2212d9dc6 100755
--- a/jbehave-core/src/main/java/org/jbehave/core/steps/Parameters.java
+++ b/jbehave-core/src/main/java/org/jbehave/core/steps/Parameters.java
@@ -8,6 +8,14 @@
*/
public interface Parameters extends Row {
+ /**
+ * Converts the parameters object to the specified type
+ *
+ * @param type the Type or Class of type <T> to convert to
+ * @return The value of type <T>
+ */
+ T as(Type type);
+
/**
* Returns the value of a named parameter as a given type
*
@@ -29,7 +37,7 @@ public interface Parameters extends Row {
T valueAs(String name, Type type, T defaultValue);
/**
- * Maps parameters to the specified type
+ * Maps parameters fields to the fields of specified type
*
* @param type The target type
* @return The object of type <T>
@@ -37,7 +45,7 @@ public interface Parameters extends Row {
T mapTo(Class type);
/**
- * Maps parameters to the specified type
+ * Maps parameters fields to the fields of specified type
*
* @param type The target type
* @param fieldNameMapping The field mapping between parameters and target type fields
diff --git a/jbehave-core/src/test/java/org/jbehave/core/steps/ConvertedParametersBehaviour.java b/jbehave-core/src/test/java/org/jbehave/core/steps/ConvertedParametersBehaviour.java
index bdbce051f..ae4cb952a 100755
--- a/jbehave-core/src/test/java/org/jbehave/core/steps/ConvertedParametersBehaviour.java
+++ b/jbehave-core/src/test/java/org/jbehave/core/steps/ConvertedParametersBehaviour.java
@@ -4,6 +4,7 @@
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -13,6 +14,7 @@
import org.jbehave.core.io.LoadFromClasspath;
import org.jbehave.core.model.TableTransformers;
import org.jbehave.core.steps.ConvertedParameters.ParametersNotMappableToType;
+import org.jbehave.core.steps.ParameterConverters.AbstractParameterConverter;
import org.junit.jupiter.api.Test;
class ConvertedParametersBehaviour {
@@ -111,6 +113,23 @@ void shouldThrowExceptionOnUnknownFieldWhileConversion() {
+ "org.jbehave.core.steps.ConvertedParametersBehaviour$Identifier"));
}
+ @Test
+ void shouldConvertParametersToPersonType() {
+ Map row = new HashMap<>();
+ row.put("years", "38");
+ row.put("firstName", "Din");
+ row.put("l_name", "Djarin");
+
+ converters.addConverters(new ParametersToPersonConverter());
+ Parameters parameters = new ConvertedParameters(row, converters);
+
+ Person person = parameters.as(Person.class);
+
+ assertThat(person.getAge(), is(38));
+ assertThat(person.getFirstName(), is("Din"));
+ assertThat(person.getLastName(), is("Djarin"));
+ }
+
public static class Identifier {
private String identifier;
@@ -121,6 +140,19 @@ public String getIdentifier() {
}
+ public static final class ParametersToPersonConverter extends AbstractParameterConverter {
+
+ @Override
+ public Person convertValue(Parameters value, Type type) {
+ Person person = new Person();
+ person.setAge(value.valueAs("years", int.class));
+ person.setFirstName(value.valueAs("firstName", String.class));
+ person.setLastName(value.valueAs("l_name", String.class));
+ return person;
+ }
+
+ }
+
public static final class Person extends Identifier {
private int age;
@@ -132,13 +164,25 @@ public int getAge() {
return age;
}
+ public void setAge(int age) {
+ this.age = age;
+ }
+
public String getFirstName() {
return firstName;
}
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
public String getLastName() {
return lastName;
}
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
}
}
diff --git a/jbehave-core/src/test/java/org/jbehave/core/steps/ParameterConvertersBehaviour.java b/jbehave-core/src/test/java/org/jbehave/core/steps/ParameterConvertersBehaviour.java
index c4c35b7ca..ab0e5cdf0 100755
--- a/jbehave-core/src/test/java/org/jbehave/core/steps/ParameterConvertersBehaviour.java
+++ b/jbehave-core/src/test/java/org/jbehave/core/steps/ParameterConvertersBehaviour.java
@@ -895,6 +895,19 @@ void shouldConvertStringViaChainOfConverters() {
assertThat(output.getOutput(), is(input + "\nfirstsecondthird"));
}
+ @Test
+ void shouldConvertFromExampleTableToTargetObject() {
+ ParameterConverters converters = new ParameterConverters();
+ converters.addConverters(new FirstParameterConverter());
+
+ String input = "|key|\n|value|";
+ ExamplesTable table = (ExamplesTable) converters.convert(input, ExamplesTable.class);
+
+ FirstConverterOutput output = (FirstConverterOutput) converters.convert(table, ExamplesTable.class,
+ FirstConverterOutput.class);
+ assertThat(output.getOutput(), is(input + "\nfirst"));
+ }
+
@Test
void shouldConvertToOptionalViaChainOfConverters() {
ParameterConverters converters = new ParameterConverters();