ConvertedParameters.java

  1. package org.jbehave.core.steps;

  2. import java.lang.reflect.Field;
  3. import java.lang.reflect.Type;
  4. import java.util.ArrayList;
  5. import java.util.Collections;
  6. import java.util.HashMap;
  7. import java.util.List;
  8. import java.util.Map;
  9. import java.util.Map.Entry;
  10. import java.util.Optional;
  11. import java.util.Set;
  12. import java.util.stream.Stream;

  13. import org.jbehave.core.annotations.Parameter;

  14. /**
  15.  * Implementation of Parameters that uses {@link ParameterConverters} to convert
  16.  * values.
  17.  */
  18. public class ConvertedParameters implements Parameters {

  19.     private final Map<String, String> values;
  20.     private final ParameterConverters parameterConverters;

  21.     /**
  22.      * Creates an instance of ConvertedParameters from a Row which provides the
  23.      * values
  24.      *
  25.      * @param row the Row to get the values from
  26.      * @param parameterConverters the ParameterConverters used for conversion
  27.      */
  28.     public ConvertedParameters(Row row, ParameterConverters parameterConverters) {
  29.         this(row.values(), parameterConverters);
  30.     }

  31.     /**
  32.      * Creates an instance of ConvertedParameters with given values
  33.      *
  34.      * @param values the Map&lt;String,String&gt; of values
  35.      * @param parameterConverters the ParameterConverters used for conversion
  36.      */
  37.     public ConvertedParameters(Map<String, String> values, ParameterConverters parameterConverters) {
  38.         this.values = values;
  39.         this.parameterConverters = parameterConverters;
  40.     }

  41.     @Override
  42.     @SuppressWarnings("unchecked")
  43.     public <T> T as(Type type) {
  44.         return (T) parameterConverters.convert(this, Parameters.class, type);
  45.     }

  46.     @Override
  47.     public <T> T valueAs(String name, Type type) {
  48.         return convert(valueFor(name), type);
  49.     }

  50.     @Override
  51.     public <T> T valueAs(String name, Type type, T defaultValue) {
  52.         if (values.containsKey(name)) {
  53.             return valueAs(name, type);
  54.         }
  55.         return defaultValue;
  56.     }

  57.     @Override
  58.     public <T> T mapTo(Class<T> type) {
  59.         return mapTo(type, Collections.emptyMap());
  60.     }

  61.     @Override
  62.     public <T> T mapTo(Class<T> type, Map<String, String> fieldNameMapping) {
  63.         try {
  64.             T instance = type.getDeclaredConstructor().newInstance();

  65.             for (Entry<String, Field> mappedField : findFields(type, values().keySet(), fieldNameMapping).entrySet()) {
  66.                 Field field = mappedField.getValue();
  67.                 Object value = valueAs(mappedField.getKey(), field.getGenericType());
  68.                 field.setAccessible(true);
  69.                 field.set(instance, value);
  70.             }

  71.             return instance;
  72.         } catch (ReflectiveOperationException e) {
  73.             throw new ParametersNotMappableToType(e);
  74.         }
  75.     }

  76.     private static Map<String, Field> findFields(Class<?> type, Set<String> fieldNames,
  77.             Map<String, String> fieldNameMapping) {

  78.         Map<String, Field> mappedFields = new HashMap<>();
  79.         List<String> unmappableFields = new ArrayList<>();

  80.         for (String fieldName : fieldNames) {
  81.             Optional<Field> fieldWrapper = findField(type, fieldName, fieldNameMapping);
  82.             if (fieldWrapper.isPresent()) {
  83.                 mappedFields.put(fieldName, fieldWrapper.get());
  84.             } else {
  85.                 unmappableFields.add(fieldName);
  86.             }
  87.         }

  88.         if (unmappableFields.isEmpty()) {
  89.             return mappedFields;
  90.         }

  91.         throw new ParametersNotMappableToType(String.format("Unable to map %s field(s) for type %s", unmappableFields,
  92.                 type));
  93.     }

  94.     private static <T> Optional<Field> findField(Class<T> type, String name, Map<String, String> fieldNameMapping) {

  95.         String mapping = fieldNameMapping.get(name);
  96.         String fieldName = mapping == null ? name : mapping;

  97.         Optional<Field> field = Stream.of(type.getDeclaredFields())
  98.                                       .filter(f -> f.isAnnotationPresent(Parameter.class))
  99.                                       .filter(f -> fieldName.equals(f.getAnnotation(Parameter.class).name()))
  100.                                       .findFirst();

  101.         return field.isPresent() ? field : findField(type, fieldName);
  102.     }

  103.     private static Optional<Field> findField(Class<?> type, String fieldName) {
  104.         for (Field field : type.getDeclaredFields()) {
  105.             if (field.getName().equals(fieldName)) {
  106.                 return Optional.of(field);
  107.             }
  108.         }

  109.         if (type.getSuperclass() != null) {
  110.             return findField(type.getSuperclass(), fieldName);
  111.         }

  112.         return Optional.empty();
  113.     }

  114.     @SuppressWarnings("unchecked")
  115.     private <T> T convert(String value, Type type) {
  116.         return (T) parameterConverters.convert(value, type);
  117.     }

  118.     private String valueFor(String name) {
  119.         if (!values.containsKey(name)) {
  120.             throw new ValueNotFound(name);
  121.         }
  122.         return values.get(name);
  123.     }

  124.     @Override
  125.     public Map<String, String> values() {
  126.         return values;
  127.     }

  128.     @SuppressWarnings("serial")
  129.     public static class ValueNotFound extends RuntimeException {

  130.         public ValueNotFound(String name) {
  131.             super(name);
  132.         }

  133.     }

  134.     @SuppressWarnings("serial")
  135.     public static class ParametersNotMappableToType extends RuntimeException {

  136.         public ParametersNotMappableToType(String message) {
  137.             super(message);
  138.         }

  139.         public ParametersNotMappableToType(Exception cause) {
  140.             super(cause);
  141.         }

  142.     }
  143. }