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