ParameterConverters.java

  1. package org.jbehave.core.steps;

  2. import static java.util.Arrays.asList;
  3. import static org.apache.commons.lang3.StringUtils.isBlank;
  4. import static org.apache.commons.lang3.Validate.isTrue;

  5. import java.io.File;
  6. import java.lang.annotation.Annotation;
  7. import java.lang.reflect.Array;
  8. import java.lang.reflect.Method;
  9. import java.lang.reflect.ParameterizedType;
  10. import java.lang.reflect.Type;
  11. import java.lang.reflect.TypeVariable;
  12. import java.math.BigDecimal;
  13. import java.math.BigInteger;
  14. import java.nio.file.Path;
  15. import java.nio.file.Paths;
  16. import java.text.DateFormat;
  17. import java.text.DecimalFormat;
  18. import java.text.DecimalFormatSymbols;
  19. import java.text.NumberFormat;
  20. import java.text.ParseException;
  21. import java.text.SimpleDateFormat;
  22. import java.time.Duration;
  23. import java.time.Instant;
  24. import java.time.LocalDate;
  25. import java.time.LocalDateTime;
  26. import java.time.LocalTime;
  27. import java.time.MonthDay;
  28. import java.time.OffsetDateTime;
  29. import java.time.OffsetTime;
  30. import java.time.Period;
  31. import java.time.Year;
  32. import java.time.YearMonth;
  33. import java.time.ZoneId;
  34. import java.time.ZoneOffset;
  35. import java.time.ZonedDateTime;
  36. import java.util.ArrayList;
  37. import java.util.Arrays;
  38. import java.util.Collection;
  39. import java.util.Collections;
  40. import java.util.Currency;
  41. import java.util.Date;
  42. import java.util.HashSet;
  43. import java.util.LinkedList;
  44. import java.util.List;
  45. import java.util.Locale;
  46. import java.util.Map;
  47. import java.util.NavigableSet;
  48. import java.util.Optional;
  49. import java.util.OptionalDouble;
  50. import java.util.OptionalInt;
  51. import java.util.OptionalLong;
  52. import java.util.Queue;
  53. import java.util.Set;
  54. import java.util.SortedSet;
  55. import java.util.TreeSet;
  56. import java.util.concurrent.CopyOnWriteArrayList;
  57. import java.util.concurrent.atomic.AtomicInteger;
  58. import java.util.concurrent.atomic.AtomicLong;
  59. import java.util.function.Function;
  60. import java.util.function.Predicate;
  61. import java.util.regex.Pattern;
  62. import java.util.stream.Collectors;

  63. import com.google.gson.Gson;

  64. import org.apache.commons.lang3.BooleanUtils;
  65. import org.apache.commons.lang3.reflect.TypeUtils;
  66. import org.jbehave.core.annotations.AsJson;
  67. import org.jbehave.core.annotations.AsParameters;
  68. import org.jbehave.core.configuration.Configuration;
  69. import org.jbehave.core.configuration.Keywords;
  70. import org.jbehave.core.configuration.MostUsefulConfiguration;
  71. import org.jbehave.core.i18n.LocalizedKeywords;
  72. import org.jbehave.core.io.LoadFromClasspath;
  73. import org.jbehave.core.io.ResourceLoader;
  74. import org.jbehave.core.model.ExamplesTable;
  75. import org.jbehave.core.model.ExamplesTableFactory;
  76. import org.jbehave.core.model.TableParsers;
  77. import org.jbehave.core.model.TableTransformers;
  78. import org.jbehave.core.model.Verbatim;

  79. /**
  80.  * <p>
  81.  * Facade responsible for converting parameter values to Java objects. It allows
  82.  * the registration of several {@link ParameterConverter} instances, and the
  83.  * first one that is found to matches the appropriate parameter type is used.
  84.  * </p>
  85.  * <p>
  86.  * Converters for several Java types are provided out-of-the-box:
  87.  * <ul>
  88.  * <li>{@link ParameterConverters.NumberConverter NumberConverter}</li>
  89.  * <li>{@link ParameterConverters.NumberListConverter NumberListConverter}</li>
  90.  * <li>{@link ParameterConverters.StringConverter StringConverter}</li>
  91.  * <li>{@link ParameterConverters.StringListConverter StringListConverter}</li>
  92.  * <li>{@link ParameterConverters.DateConverter DateConverter}</li>
  93.  * <li>{@link ParameterConverters.ExamplesTableConverter ExamplesTableConverter}</li>
  94.  * <li>{@link ParameterConverters.ExamplesTableParametersConverter ExamplesTableParametersConverter}</li>
  95.  * <li>{@link ParameterConverters.MethodReturningConverter MethodReturningConverter}</li>
  96.  * <li>{@link ParameterConverters.JsonConverter JsonConverter}</li>
  97.  * </ul>
  98.  * </p>
  99.  */
  100. public class ParameterConverters {

  101.     public static final StepMonitor DEFAULT_STEP_MONITOR = new SilentStepMonitor();
  102.     public static final Locale DEFAULT_NUMBER_FORMAT_LOCAL = Locale.ENGLISH;
  103.     public static final String DEFAULT_COLLECTION_SEPARATOR = ",";

  104.     public static final boolean DEFAULT_THREAD_SAFETY = true;

  105.     private static final String DEFAULT_TRUE_VALUE = "true";
  106.     private static final String DEFAULT_FALSE_VALUE = "false";

  107.     private final StepMonitor monitor;
  108.     private final List<ParameterConverter> converters;
  109.     private final boolean threadSafe;
  110.     private String escapedCollectionSeparator;


  111.     /**
  112.      * Creates a ParameterConverters using the default resource loader and table transformers,
  113.      * a SilentStepMonitor, English as Locale and "," as collection separator.
  114.      */
  115.     public ParameterConverters() {
  116.         this(new LoadFromClasspath(), new TableTransformers());
  117.     }

  118.     /**
  119.      * Creates a ParameterConverters using the given table transformers.
  120.      *
  121.      * @param tableTransformers the table transformers
  122.      */
  123.     public ParameterConverters(TableTransformers tableTransformers) {
  124.         this(new LoadFromClasspath(), tableTransformers);
  125.     }

  126.     /**
  127.      * Creates a ParameterConverters of ParameterConverters using the given resource loader.
  128.      *
  129.      * @param resourceLoader the resource loader
  130.      */
  131.     public ParameterConverters(ResourceLoader resourceLoader) {
  132.         this(resourceLoader, new TableTransformers());
  133.     }

  134.     /**
  135.      * Creates a ParameterConverters given resource loader and table transformers.
  136.      *
  137.      * @param resourceLoader the resource loader
  138.      * @param tableTransformers the table transformers
  139.      */
  140.     public ParameterConverters(ResourceLoader resourceLoader, TableTransformers tableTransformers) {
  141.         this(DEFAULT_STEP_MONITOR, resourceLoader, new ParameterControls(), tableTransformers);
  142.     }

  143.     /**
  144.      * Creates a ParameterConverters using given StepMonitor, resource loader and table transformers.
  145.      *
  146.      * @param monitor the StepMonitor to use
  147.      * @param resourceLoader the resource loader
  148.      * @param parameterControls the parameter controls
  149.      * @param tableTransformers the table transformers
  150.      */
  151.     public ParameterConverters(StepMonitor monitor, ResourceLoader resourceLoader, ParameterControls parameterControls,
  152.             TableTransformers tableTransformers) {
  153.         this(monitor, resourceLoader, parameterControls, tableTransformers, DEFAULT_NUMBER_FORMAT_LOCAL,
  154.                 DEFAULT_COLLECTION_SEPARATOR, DEFAULT_THREAD_SAFETY);
  155.     }

  156.     /**
  157.      * Creates a ParameterConverters using given StepMonitor, keywords, resource loader and table transformers.
  158.      *
  159.      * @param monitor the StepMonitor to use
  160.      * @param keywords the keywords to use
  161.      * @param resourceLoader the resource loader
  162.      * @param parameterControls the parameter controls
  163.      * @param tableTransformers the table transformers
  164.      */
  165.     public ParameterConverters(StepMonitor monitor, Keywords keywords, ResourceLoader resourceLoader,
  166.             ParameterControls parameterControls, TableTransformers tableTransformers) {
  167.         this(monitor, keywords, resourceLoader, parameterControls, tableTransformers, DEFAULT_NUMBER_FORMAT_LOCAL,
  168.                 DEFAULT_COLLECTION_SEPARATOR, DEFAULT_THREAD_SAFETY);
  169.     }

  170.     /**
  171.      * Create a ParameterConverters with given thread-safety
  172.      *
  173.      * @param resourceLoader    the resource loader
  174.      * @param parameterControls the parameter controls
  175.      * @param tableTransformers the table transformers
  176.      * @param threadSafe        the boolean flag to determine if access to {@link ParameterConverter} should be
  177.      *                          thread-safe
  178.      */
  179.     public ParameterConverters(ResourceLoader resourceLoader, ParameterControls parameterControls,
  180.             TableTransformers tableTransformers, boolean threadSafe) {
  181.         this(DEFAULT_STEP_MONITOR, resourceLoader, parameterControls, tableTransformers, DEFAULT_NUMBER_FORMAT_LOCAL,
  182.                 DEFAULT_COLLECTION_SEPARATOR, threadSafe);
  183.     }

  184.     /**
  185.      * Creates a ParameterConverters for the given StepMonitor, Locale, list
  186.      * separator and thread-safety. When selecting a collectionSeparator, please make
  187.      * sure that this character doesn't have a special meaning in your Locale
  188.      * (for instance "," is used as decimal separator in some Locale)
  189.      *
  190.      * @param monitor             the StepMonitor reporting the conversions
  191.      * @param resourceLoader      the resource loader
  192.      * @param parameterControls   the parameter controls
  193.      * @param tableTransformers   the table transformers
  194.      * @param locale              the Locale to use when reading numbers
  195.      * @param collectionSeparator the String to use as collection separator
  196.      * @param threadSafe          the boolean flag to determine if modification of {@link ParameterConverter} should be
  197.      *                            thread-safe
  198.      */
  199.     public ParameterConverters(StepMonitor monitor, ResourceLoader resourceLoader, ParameterControls parameterControls,
  200.             TableTransformers tableTransformers, Locale locale, String collectionSeparator, boolean threadSafe) {
  201.         this(monitor, new LocalizedKeywords(), resourceLoader, parameterControls, tableTransformers, locale,
  202.                 collectionSeparator, threadSafe);

  203.     }

  204.     /**
  205.      * Creates a ParameterConverters for the given StepMonitor, keywords, Locale, list
  206.      * separator and thread-safety. When selecting a collectionSeparator, please make
  207.      * sure that this character doesn't have a special meaning in your Locale
  208.      * (for instance "," is used as decimal separator in some Locale)
  209.      *
  210.      * @param monitor             the StepMonitor reporting the conversions
  211.      * @param resourceLoader      the resource loader
  212.      * @param keywords            the keywords
  213.      * @param parameterControls   the parameter controls
  214.      * @param tableTransformers   the table transformers
  215.      * @param locale              the Locale to use when reading numbers
  216.      * @param collectionSeparator the String to use as collection separator
  217.      * @param threadSafe          the boolean flag to determine if modification of {@link ParameterConverter} should be
  218.      *                            thread-safe
  219.      */
  220.     public ParameterConverters(StepMonitor monitor, Keywords keywords, ResourceLoader resourceLoader,
  221.             ParameterControls parameterControls, TableTransformers tableTransformers, Locale locale,
  222.             String collectionSeparator, boolean threadSafe) {
  223.         this(monitor, new ArrayList<>(), threadSafe);
  224.         this.addConverters(
  225.                 defaultConverters(keywords, resourceLoader, parameterControls, new TableParsers(keywords, this),
  226.                         tableTransformers, locale, collectionSeparator));
  227.     }

  228.     /**
  229.      * Creates a ParameterConverters for the given StepMonitor, keywords, Locale, list
  230.      * separator and thread-safety. When selecting a collectionSeparator, please make
  231.      * sure that this character doesn't have a special meaning in your Locale
  232.      * (for instance "," is used as decimal separator in some Locale)
  233.      *
  234.      * @param monitor             the StepMonitor reporting the conversions
  235.      * @param resourceLoader      the resource loader
  236.      * @param keywords            the keywords
  237.      * @param parameterControls   the parameter controls
  238.      * @param tableParsers        the table parsers
  239.      * @param tableTransformers   the table transformers
  240.      * @param locale              the Locale to use when reading numbers
  241.      * @param collectionSeparator the String to use as collection separator
  242.      * @param threadSafe          the boolean flag to determine if modification of {@link ParameterConverter} should be
  243.      *                            thread-safe
  244.      */
  245.     public ParameterConverters(StepMonitor monitor, Keywords keywords, ResourceLoader resourceLoader,
  246.             ParameterControls parameterControls, TableParsers tableParsers, TableTransformers tableTransformers,
  247.             Locale locale, String collectionSeparator, boolean threadSafe) {
  248.         this(monitor, new ArrayList<>(), threadSafe);
  249.         this.addConverters(
  250.                 defaultConverters(keywords, resourceLoader, parameterControls, tableParsers, tableTransformers, locale,
  251.                         collectionSeparator));
  252.     }

  253.     private ParameterConverters(StepMonitor monitor, List<ParameterConverter> converters, boolean threadSafe) {
  254.         this.monitor = monitor;
  255.         this.threadSafe = threadSafe;
  256.         this.converters = threadSafe ? new CopyOnWriteArrayList<>(converters)
  257.                 : new ArrayList<>(converters);
  258.     }

  259.     protected ParameterConverter[] defaultConverters(Keywords keywords, ResourceLoader resourceLoader,
  260.             ParameterControls parameterControls, TableParsers tableParsers, TableTransformers tableTransformers,
  261.             Locale locale, String collectionSeparator) {
  262.         this.escapedCollectionSeparator = escapeRegexPunctuation(collectionSeparator);
  263.         ExamplesTableFactory tableFactory = new ExamplesTableFactory(keywords, resourceLoader, this, parameterControls,
  264.                 tableParsers, tableTransformers);
  265.         JsonFactory jsonFactory = new JsonFactory();
  266.         return new ParameterConverter[] {
  267.             new BooleanConverter(),
  268.             new NumberConverter(NumberFormat.getInstance(locale)),
  269.             new StringConverter(),
  270.             new StringListConverter(escapedCollectionSeparator),
  271.             new DateConverter(),
  272.             new EnumConverter(),
  273.             new ExamplesTableConverter(tableFactory),
  274.             new ExamplesTableParametersConverter(tableFactory),
  275.             new JsonConverter(jsonFactory),
  276.             new FunctionalParameterConverter<>(String.class, Path.class, Paths::get),
  277.             new FunctionalParameterConverter<>(String.class, Currency.class, Currency::getInstance),
  278.             new FunctionalParameterConverter<>(String.class, Pattern.class, Pattern::compile),
  279.             new FunctionalParameterConverter<>(String.class, File.class, File::new),
  280.             new FunctionalParameterConverter<>(String.class, Verbatim.class, Verbatim::new),

  281.             // java.time.* converters
  282.             new FunctionalParameterConverter<>(String.class, Duration.class, Duration::parse),
  283.             new FunctionalParameterConverter<>(String.class, Instant.class, Instant::parse),
  284.             new FunctionalParameterConverter<>(String.class, LocalDate.class, LocalDate::parse),
  285.             new FunctionalParameterConverter<>(String.class, LocalDateTime.class, LocalDateTime::parse),
  286.             new FunctionalParameterConverter<>(String.class, LocalTime.class, LocalTime::parse),
  287.             new FunctionalParameterConverter<>(String.class, MonthDay.class, MonthDay::parse),
  288.             new FunctionalParameterConverter<>(String.class, OffsetDateTime.class, OffsetDateTime::parse),
  289.             new FunctionalParameterConverter<>(String.class, OffsetTime.class, OffsetTime::parse),
  290.             new FunctionalParameterConverter<>(String.class, Period.class, Period::parse),
  291.             new FunctionalParameterConverter<>(String.class, Year.class, Year::parse),
  292.             new FunctionalParameterConverter<>(String.class, YearMonth.class, YearMonth::parse),
  293.             new FunctionalParameterConverter<>(String.class, ZonedDateTime.class, ZonedDateTime::parse),
  294.             new FunctionalParameterConverter<>(String.class, ZoneId.class, ZoneId::of),
  295.             new FunctionalParameterConverter<>(String.class, ZoneOffset.class, ZoneOffset::of),

  296.             // Converters for Optional types
  297.             new FunctionalParameterConverter<>(String.class, OptionalDouble.class,
  298.                     value -> OptionalDouble.of((double) this.convert(value, double.class))
  299.             ),
  300.             new FunctionalParameterConverter<>(String.class, OptionalInt.class,
  301.                     value -> OptionalInt.of((int) this.convert(value, int.class))
  302.             ),
  303.             new FunctionalParameterConverter<>(String.class, OptionalLong.class,
  304.                     value -> OptionalLong.of((long) this.convert(value, long.class))
  305.             )
  306.         };
  307.     }

  308.     // TODO : This is a duplicate from RegExpPrefixCapturing
  309.     private String escapeRegexPunctuation(String matchThis) {
  310.         return matchThis.replaceAll("([\\[\\]\\{\\}\\?\\^\\.\\*\\(\\)\\+\\\\])", "\\\\$1");
  311.     }

  312.     public ParameterConverters addConverters(ParameterConverter... converters) {
  313.         return addConverters(asList(converters));
  314.     }

  315.     public ParameterConverters addConverters(List<? extends ParameterConverter> converters) {
  316.         this.converters.addAll(0, converters);
  317.         return this;
  318.     }

  319.     private static boolean isChainComplete(Queue<ParameterConverter> convertersChain,
  320.             Predicate<Type> sourceTypePredicate) {
  321.         return !convertersChain.isEmpty() && sourceTypePredicate.test(convertersChain.peek().getSourceType());
  322.     }

  323.     private static Object applyConverters(Object value, Type basicType, Queue<ParameterConverter> convertersChain) {
  324.         Object identity = convertersChain.peek().convertValue(value, basicType);
  325.         return convertersChain.stream().skip(1).reduce(identity,
  326.                 (v, c) -> c.convertValue(v, c.getTargetType()), (l, r) -> l);
  327.     }

  328.     public Object convert(String value, Type type) {
  329.         return convert(value, String.class, type);
  330.     }

  331.     @SuppressWarnings({ "unchecked", "rawtypes" })
  332.     public Object convert(Object value, Class<?> source, Type type) {
  333.         Predicate<Type> sourceTypePredicate = t -> source.isAssignableFrom((Class<?>) t);
  334.         Queue<ParameterConverter> converters = findConverters(type, sourceTypePredicate);
  335.         if (isChainComplete(converters, sourceTypePredicate)) {
  336.             Object converted = applyConverters(value, type, converters);
  337.             Queue<Class<?>> classes = converters.stream().map(ParameterConverter::getClass)
  338.                     .collect(Collectors.toCollection(LinkedList::new));
  339.             monitor.convertedValueOfType(String.valueOf(value), type, converted, classes);
  340.             return converted;
  341.         }

  342.         if (isAssignableFromRawType(Optional.class, type)) {
  343.             Type elementType = argumentType(type);
  344.             return Optional.of(convert(value, source, elementType));
  345.         }

  346.         if (isAssignableFromRawType(Collection.class, type)) {
  347.             Type elementType = argumentType(type);
  348.             Collection collection = createCollection(rawClass(type));

  349.             if (collection != null) {
  350.                 Queue<ParameterConverter> typeConverters = findConverters(elementType, sourceTypePredicate);

  351.                 if (!typeConverters.isEmpty()) {
  352.                     Type sourceType = typeConverters.peek().getSourceType();

  353.                     if (String.class.isAssignableFrom((Class<?>) sourceType)) {
  354.                         fillCollection((String) value, escapedCollectionSeparator, typeConverters, elementType,
  355.                                 collection);
  356.                     } else if (isAssignableFrom(Parameters.class, sourceType)) {
  357.                         ExamplesTable table = (ExamplesTable) findBaseConverter(ExamplesTable.class).convertValue(value,
  358.                                 String.class);
  359.                         fillCollection(table.getRowsAsParameters(), typeConverters, elementType, collection);
  360.                     }

  361.                     return collection;
  362.                 }
  363.             }
  364.         }

  365.         if (type instanceof Class) {
  366.             Class clazz = (Class) type;
  367.             if (clazz.isArray()) {
  368.                 String[] elements = parseElements((String) value, escapedCollectionSeparator);
  369.                 Class elementType = clazz.getComponentType();
  370.                 ParameterConverter elementConverter = findBaseConverter(elementType);
  371.                 Object array = createArray(elementType, elements.length);

  372.                 if (elementConverter != null && array != null) {
  373.                     fillArray(elements, elementConverter, elementType, array);
  374.                     return array;
  375.                 }                
  376.             }
  377.         }

  378.         throw new ParameterConversionFailed("No parameter converter for " + type);
  379.     }

  380.     private ParameterConverter findBaseConverter(Type type) {
  381.         for (ParameterConverter converter : converters) {
  382.             if (converter.canConvertFrom(String.class) && converter.canConvertTo(type)) {
  383.                 return converter;
  384.             }
  385.         }
  386.         return null;
  387.     }

  388.     private Queue<ParameterConverter> findConverters(Type type, Predicate<Type> sourceTypePredicate) {
  389.         LinkedList<ParameterConverter> convertersChain = new LinkedList<>();
  390.         putConverters(type, convertersChain, sourceTypePredicate);
  391.         return convertersChain;
  392.     }

  393.     private void putConverters(Type type, LinkedList<ParameterConverter> container,
  394.             Predicate<Type> sourceTypePredicate) {
  395.         for (ParameterConverter converter : converters) {
  396.             if (converter.canConvertTo(type)) {
  397.                 container.addFirst(converter);
  398.                 Type sourceType = converter.getSourceType();
  399.                 if (sourceTypePredicate.test(sourceType)) {
  400.                     break;
  401.                 }
  402.                 putConverters(sourceType, container, sourceTypePredicate);
  403.             }
  404.         }
  405.     }

  406.     private static boolean isAssignableFrom(Class<?> clazz, Type type) {
  407.         return type instanceof Class<?> && clazz.isAssignableFrom((Class<?>) type);
  408.     }

  409.     private static boolean isAssignableFromRawType(Class<?> clazz, Type type) {
  410.         return type instanceof ParameterizedType && isAssignableFrom(clazz, ((ParameterizedType) type).getRawType());
  411.     }

  412.     private static Class<?> rawClass(Type type) {
  413.         return (Class<?>) ((ParameterizedType) type).getRawType();
  414.     }

  415.     private static Class<?> argumentClass(Type type) {
  416.         if (type instanceof ParameterizedType) {
  417.             Type typeArgument = ((ParameterizedType) type).getActualTypeArguments()[0];
  418.             return typeArgument instanceof ParameterizedType ? rawClass(typeArgument) : (Class<?>) typeArgument;
  419.         } else {
  420.             return (Class<?>) type;
  421.         }
  422.     }

  423.     private static Type argumentType(Type type) {
  424.         return ((ParameterizedType) type).getActualTypeArguments()[0];
  425.     }

  426.     private static boolean isAnnotationPresent(Type type, Class<? extends Annotation> annotationClass) {
  427.         if (type instanceof ParameterizedType) {
  428.             return rawClass(type).isAnnotationPresent(annotationClass) || argumentClass(type).isAnnotationPresent(
  429.                     annotationClass);
  430.         }
  431.         return type instanceof Class && ((Class<?>) type).isAnnotationPresent(annotationClass);
  432.     }

  433.     private static String[] parseElements(String value, String elementSeparator) {
  434.         String[] elements = value.trim().isEmpty() ? new String[0] : value.split(elementSeparator);
  435.         Arrays.setAll(elements, i -> elements[i].trim());
  436.         return elements;
  437.     }

  438.     private static void fillCollection(String value, String elementSeparator, Queue<ParameterConverter> convertersChain,
  439.             Type elementType, Collection convertedValues) {
  440.         fillCollection(asList(parseElements(value, elementSeparator)), convertersChain, elementType, convertedValues);
  441.     }

  442.     private static void fillCollection(Collection elements, Queue<ParameterConverter> convertersChain,
  443.             Type elementType, Collection convertedValues) {
  444.         for (Object element : elements) {
  445.             Object convertedValue = applyConverters(element, elementType, convertersChain);
  446.             convertedValues.add(convertedValue);
  447.         }
  448.     }

  449.     private static <T> void fillArray(String[] elements, ParameterConverter<String, T> elementConverter,
  450.             Type elementType, Object convertedValues) {
  451.         for (int i = 0; i < elements.length; i++) {
  452.             T convertedValue = elementConverter.convertValue(elements[i], elementType);
  453.             Array.set(convertedValues, i, convertedValue);
  454.         }
  455.     }

  456.     @SuppressWarnings("unchecked")
  457.     private static <E> Collection<E> createCollection(Class<?> collectionType) {
  458.         if (collectionType.isInterface()) {
  459.             if (Set.class == collectionType) {
  460.                 return new HashSet<>();
  461.             } else if (List.class == collectionType) {
  462.                 return new ArrayList<>();
  463.             } else if (SortedSet.class == collectionType || NavigableSet.class == collectionType) {
  464.                 return new TreeSet<>();
  465.             }
  466.         }
  467.         try {
  468.             return (Collection<E>) collectionType.getConstructor().newInstance();
  469.         } catch (@SuppressWarnings("unused") Throwable t) {
  470.             // Could not instantiate Collection type, swallowing exception quietly
  471.         }
  472.         return null;
  473.     }

  474.     private static Object createArray(Class<?> elementType, int length) {
  475.         try {
  476.             return Array.newInstance(elementType, length);
  477.         } catch (Throwable e) {
  478.             // Could not instantiate array, swallowing exception quietly
  479.         }

  480.         return null;
  481.     }

  482.     public ParameterConverters newInstanceAdding(ParameterConverter converter) {
  483.         List<ParameterConverter> convertersForNewInstance = new ArrayList<>(converters);
  484.         convertersForNewInstance.add(converter);
  485.         return new ParameterConverters(monitor, convertersForNewInstance, threadSafe);
  486.     }

  487.     /**
  488.      * A parameter converter for generic type of source input and target output.
  489.      * The converters can be chained to allow for the target of one converter
  490.      * can be used as the source for another.
  491.      *
  492.      * @param <T> the target converted output
  493.      * @param <S> the source input value
  494.      */
  495.     public interface ParameterConverter<S, T> {

  496.         /**
  497.          * Return {@code true} if the converter can convert to the desired target type.
  498.          * @param type the type descriptor that describes the requested result type
  499.          * @return {@code true} if that conversion can be performed
  500.          */
  501.         boolean canConvertTo(Type type);

  502.         /**
  503.          * Return {@code true} if the converter can convert from the desired target type.
  504.          * @param type the type descriptor that describes the source type
  505.          * @return {@code true} if that conversion can be performed
  506.          */
  507.         boolean canConvertFrom(Type type);

  508.         /**
  509.          * Convert the value from one type to another, for example from a {@code boolean} to a {@code String}.
  510.          * @param value the value to be converted
  511.          * @param type the type descriptor that supplies extra information about the requested result type
  512.          * @return the converted value
  513.          */
  514.         T convertValue(S value, Type type);

  515.         Type getSourceType();

  516.         Type getTargetType();
  517.     }

  518.     @SuppressWarnings("serial")
  519.     public static class ParameterConversionFailed extends RuntimeException {

  520.         public ParameterConversionFailed(String message) {
  521.             super(message);
  522.         }

  523.         public ParameterConversionFailed(String message, Throwable cause) {
  524.             super(message, cause);
  525.         }
  526.     }

  527.     public abstract static class FromStringParameterConverter<T> extends AbstractParameterConverter<String, T> {
  528.         public FromStringParameterConverter() {
  529.         }

  530.         public FromStringParameterConverter(Type targetType) {
  531.             super(String.class, targetType);
  532.         }
  533.     }

  534.     public abstract static class AbstractParameterConverter<S, T> implements ParameterConverter<S, T> {

  535.         private final Type sourceType;
  536.         private final Type targetType;

  537.         public AbstractParameterConverter() {
  538.             Map<TypeVariable<?>, Type> types = TypeUtils.getTypeArguments(getClass(), ParameterConverter.class);
  539.             TypeVariable<?>[] typeVariables = ParameterConverter.class.getTypeParameters();
  540.             this.sourceType = types.get(typeVariables[0]);
  541.             this.targetType = types.get(typeVariables[1]);
  542.         }

  543.         public AbstractParameterConverter(Type sourceType, Type targetType) {
  544.             this.sourceType = sourceType;
  545.             this.targetType = targetType;
  546.         }

  547.         @Override
  548.         public boolean canConvertTo(Type type) {
  549.             return isAssignable(targetType, type);
  550.         }

  551.         @Override
  552.         public boolean canConvertFrom(Type type) {
  553.             return isAssignable(sourceType, type);
  554.         }

  555.         public Type getSourceType() {
  556.             return sourceType;
  557.         }

  558.         public Type getTargetType() {
  559.             return targetType;
  560.         }

  561.         private static boolean isAssignable(Type from, Type to) {
  562.             if (from instanceof Class<?>) {
  563.                 return isAssignableFrom((Class<?>) from, to);
  564.             }
  565.             return from.equals(to);
  566.         }
  567.     }

  568.     public static class FunctionalParameterConverter<S, T> extends AbstractParameterConverter<S, T> {

  569.         private final Function<S, T> converterFunction;

  570.         public FunctionalParameterConverter(Class<S> sourceType, Class<T> targetType,
  571.                 Function<S, T> converterFunction) {
  572.             super(sourceType, targetType);
  573.             this.converterFunction = converterFunction;
  574.         }

  575.         protected FunctionalParameterConverter(Function<S, T> converterFunction) {
  576.             this.converterFunction = converterFunction;
  577.         }

  578.         @Override
  579.         public T convertValue(S value, Type type) {
  580.             return converterFunction.apply(value);
  581.         }
  582.     }

  583.     public abstract static class AbstractListParameterConverter<T> extends FromStringParameterConverter<List<T>> {

  584.         private final String valueSeparator;
  585.         private final Queue<ParameterConverter> elementConverters;

  586.         public AbstractListParameterConverter(String valueSeparator, FromStringParameterConverter<T> elementConverter) {
  587.             this.valueSeparator = valueSeparator;
  588.             this.elementConverters = new LinkedList<>();
  589.             this.elementConverters.add(elementConverter);
  590.         }

  591.         @Override
  592.         public boolean canConvertTo(Type type) {
  593.             return isAssignableFromRawType(List.class, type) && elementConverters.peek().canConvertTo(
  594.                     argumentType(type));
  595.         }

  596.         @Override
  597.         public List<T> convertValue(String value, Type type) {
  598.             Type elementType = argumentType(type);
  599.             List<T> convertedValues = new ArrayList<>();
  600.             fillCollection(value, valueSeparator, elementConverters, elementType, convertedValues);
  601.             return convertedValues;
  602.         }
  603.     }

  604.     /**
  605.      * Converts values to numbers, supporting any subclass of {@link Number} (including generic Number type), and it
  606.      * unboxed counterpart, using a {@link NumberFormat} to parse to a {@link Number} and to convert it to a specific
  607.      * number type:
  608.      * <ul>
  609.      * <li>Byte, byte: {@link Number#byteValue()}</li>
  610.      * <li>Short, short: {@link Number#shortValue()}</li>
  611.      * <li>Integer, int: {@link Number#intValue()}</li>
  612.      * <li>Float, float: {@link Number#floatValue()}</li>
  613.      * <li>Long, long: {@link Number#longValue()}</li>
  614.      * <li>Double, double: {@link Number#doubleValue()}</li>
  615.      * <li>BigInteger: {@link BigInteger#valueOf(long)}</li>
  616.      * <li>BigDecimal: {@link BigDecimal#valueOf(double)}</li>
  617.      * </ul>
  618.      * If no number format is provided, it defaults to {@link NumberFormat#getInstance()}.
  619.      * <p>The localized instance {@link NumberFormat#getInstance(Locale)} can be used to convert numbers in specific
  620.      * locales.</p>
  621.      */
  622.     public static class NumberConverter extends FromStringParameterConverter<Number> {
  623.         private static List<Class<?>> primitiveTypes = asList(byte.class, short.class, int.class, float.class,
  624.                 long.class, double.class);

  625.         private final NumberFormat numberFormat;
  626.         private ThreadLocal<NumberFormat> threadLocalNumberFormat = new ThreadLocal<>();

  627.         public NumberConverter() {
  628.             this(NumberFormat.getInstance(DEFAULT_NUMBER_FORMAT_LOCAL));
  629.         }

  630.         public NumberConverter(NumberFormat numberFormat) {
  631.             synchronized (this) {
  632.                 this.numberFormat = numberFormat;
  633.                 this.threadLocalNumberFormat.set((NumberFormat) this.numberFormat.clone());
  634.             }
  635.         }

  636.         @Override
  637.         public boolean canConvertTo(Type type) {
  638.             return super.canConvertTo(type) || primitiveTypes.contains(type);
  639.         }

  640.         @Override
  641.         public Number convertValue(String value, Type type) {
  642.             try {
  643.                 Number n = numberFormat().parse(value);
  644.                 if (type == Byte.class || type == byte.class) {
  645.                     return n.byteValue();
  646.                 } else if (type == Short.class || type == short.class) {
  647.                     return n.shortValue();
  648.                 } else if (type == Integer.class || type == int.class) {
  649.                     return n.intValue();
  650.                 } else if (type == Float.class || type == float.class) {
  651.                     return n.floatValue();
  652.                 } else if (type == Long.class || type == long.class) {
  653.                     return n.longValue();
  654.                 } else if (type == Double.class || type == double.class) {
  655.                     return n.doubleValue();
  656.                 } else if (type == BigInteger.class) {
  657.                     return BigInteger.valueOf(n.longValue());
  658.                 } else if (type == BigDecimal.class) {
  659.                     return new BigDecimal(canonicalize(value));
  660.                 } else if (type == AtomicInteger.class) {
  661.                     return new AtomicInteger(Integer.parseInt(value));
  662.                 } else if (type == AtomicLong.class) {
  663.                     return new AtomicLong(Long.parseLong(value));
  664.                 } else {
  665.                     return n;
  666.                 }
  667.             } catch (NumberFormatException | ParseException e) {
  668.                 throw new ParameterConversionFailed(value, e);
  669.             }
  670.         }

  671.         /**
  672.          * Return NumberFormat instance with preferred locale threadsafe
  673.          *
  674.          * @return A threadlocal version of original NumberFormat instance
  675.          */
  676.         private NumberFormat numberFormat() {
  677.             if (threadLocalNumberFormat.get() == null) {
  678.                 synchronized (this) {
  679.                     threadLocalNumberFormat.set((NumberFormat) numberFormat.clone());
  680.                 }
  681.             }
  682.             return threadLocalNumberFormat.get();
  683.         }

  684.         /**
  685.          * Canonicalize a number representation to a format suitable for the {@link BigDecimal#BigDecimal(String)}
  686.          * constructor, taking into account the settings of the currently configured DecimalFormat.
  687.          *
  688.          * @param value a localized number value
  689.          * @return A canonicalized string value suitable for consumption by BigDecimal
  690.          */
  691.         private String canonicalize(String value) {
  692.             char decimalPointSeparator = '.'; // default
  693.             char minusSign = '-'; // default
  694.             String rxNotDigits = "[\\.,]";
  695.             StringBuilder builder = new StringBuilder(value.length());

  696.             // override defaults according to numberFormat's settings
  697.             if (numberFormat() instanceof DecimalFormat) {
  698.                 DecimalFormatSymbols decimalFormatSymbols = ((DecimalFormat) numberFormat()).getDecimalFormatSymbols();
  699.                 minusSign = decimalFormatSymbols.getMinusSign();
  700.                 decimalPointSeparator = decimalFormatSymbols.getDecimalSeparator();
  701.             }

  702.             value = value.trim();
  703.             int decimalPointPosition = value.lastIndexOf(decimalPointSeparator);
  704.             int firstDecimalPointPosition = value.indexOf(decimalPointSeparator);

  705.             if (firstDecimalPointPosition != decimalPointPosition) {
  706.                 throw new NumberFormatException("Invalid format, more than one decimal point has been found.");
  707.             }

  708.             if (decimalPointPosition != -1) {
  709.                 String sf = value.substring(0, decimalPointPosition).replaceAll(rxNotDigits, "");
  710.                 String dp = value.substring(decimalPointPosition + 1).replaceAll(rxNotDigits, "");

  711.                 builder.append(sf);
  712.                 builder.append('.'); // fixed "." for BigDecimal constructor
  713.                 builder.append(dp);

  714.             } else {
  715.                 builder.append(value.replaceAll(rxNotDigits, ""));
  716.             }

  717.             boolean isNegative = value.charAt(0) == minusSign;

  718.             if (isNegative) {
  719.                 builder.setCharAt(0, '-'); // fixed "-" for BigDecimal constructor
  720.             }
  721.             return builder.toString();
  722.         }
  723.     }

  724.     /**
  725.      * Converts value to list of numbers. Splits value to a list, using an
  726.      * injectable value separator (defaulting to ",") and converts each element
  727.      * of list via the {@link NumberConverter}, using the {@link NumberFormat}
  728.      * provided (defaulting to {@link NumberFormat#getInstance()}
  729.      * ).
  730.      */
  731.     public static class NumberListConverter extends AbstractListParameterConverter<Number> {

  732.         public NumberListConverter() {
  733.             this(NumberFormat.getInstance(DEFAULT_NUMBER_FORMAT_LOCAL), DEFAULT_COLLECTION_SEPARATOR);
  734.         }

  735.         /**
  736.          * Creates converter for a list of numbers using the specified number format and value separator
  737.          *
  738.          * @param numberFormat   Specific NumberFormat to use.
  739.          * @param valueSeparator A regexp to use as list separate
  740.          */
  741.         public NumberListConverter(NumberFormat numberFormat, String valueSeparator) {
  742.             super(valueSeparator, new NumberConverter(numberFormat));
  743.         }
  744.     }

  745.     public static class StringConverter extends FromStringParameterConverter<String> {
  746.         private static final String NEWLINES_PATTERN = "(\n)|(\r\n)";
  747.         private static final String SYSTEM_NEWLINE = System.getProperty("line.separator");

  748.         @Override
  749.         public String convertValue(String value, Type type) {
  750.             return value != null ? value.replaceAll(NEWLINES_PATTERN, SYSTEM_NEWLINE) : null;
  751.         }
  752.     }

  753.     /**
  754.      * Converts value to list of String. Splits value to a list, using an
  755.      * injectable value separator (defaults to ",") and trimming each element of
  756.      * the list.
  757.      */
  758.     public static class StringListConverter extends AbstractListParameterConverter<String> {

  759.         public StringListConverter() {
  760.             this(DEFAULT_COLLECTION_SEPARATOR);
  761.         }

  762.         /**
  763.          * Creates converter for a list of strings using the value separator
  764.          *
  765.          * @param valueSeparator A regexp to use as list separator
  766.          */
  767.         public StringListConverter(String valueSeparator) {
  768.             super(valueSeparator, new StringConverter());
  769.         }

  770.         @Override
  771.         public List<String> convertValue(String value, Type type) {
  772.             if (value.trim().isEmpty()) {
  773.                 return Collections.emptyList();
  774.             }
  775.             return super.convertValue(value, type);
  776.         }
  777.     }

  778.     /**
  779.      * Parses value to a {@link Date} using an injectable {@link DateFormat}
  780.      * (defaults to <b>new SimpleDateFormat("dd/MM/yyyy")</b>)
  781.      */
  782.     public static class DateConverter extends FromStringParameterConverter<Date> {

  783.         public static final DateFormat DEFAULT_FORMAT = new SimpleDateFormat("dd/MM/yyyy");

  784.         private final DateFormat dateFormat;

  785.         public DateConverter() {
  786.             this(DEFAULT_FORMAT);
  787.         }

  788.         public DateConverter(DateFormat dateFormat) {
  789.             this.dateFormat = dateFormat;
  790.         }

  791.         @Override
  792.         public Date convertValue(String value, Type type) {
  793.             try {
  794.                 return dateFormat.parse(value);
  795.             } catch (ParseException e) {
  796.                 throw new ParameterConversionFailed("Failed to convert value " + value + " with date format "
  797.                         + (dateFormat instanceof SimpleDateFormat ? ((SimpleDateFormat) dateFormat).toPattern()
  798.                                 : dateFormat), e);
  799.             }
  800.         }
  801.     }

  802.     public static class BooleanConverter extends FromStringParameterConverter<Boolean> {
  803.         private final String trueValue;
  804.         private final String falseValue;

  805.         public BooleanConverter() {
  806.             this(DEFAULT_TRUE_VALUE, DEFAULT_FALSE_VALUE);
  807.         }

  808.         public BooleanConverter(String trueValue, String falseValue) {
  809.             this.trueValue = trueValue;
  810.             this.falseValue = falseValue;
  811.         }

  812.         @Override
  813.         public boolean canConvertTo(Type type) {
  814.             return super.canConvertTo(type) || isAssignableFrom(Boolean.TYPE, type);
  815.         }

  816.         @Override
  817.         public Boolean convertValue(String value, Type type) {
  818.             try {
  819.                 return BooleanUtils.toBoolean(value, trueValue, falseValue);
  820.             } catch (IllegalArgumentException e) {
  821.                 return false;
  822.             }
  823.         }
  824.     }

  825.     public static class BooleanListConverter extends AbstractListParameterConverter<Boolean> {

  826.         public BooleanListConverter() {
  827.             this(DEFAULT_COLLECTION_SEPARATOR, DEFAULT_TRUE_VALUE, DEFAULT_FALSE_VALUE);
  828.         }

  829.         public BooleanListConverter(String valueSeparator) {
  830.             this(valueSeparator, DEFAULT_TRUE_VALUE, DEFAULT_FALSE_VALUE);
  831.         }

  832.         public BooleanListConverter(String valueSeparator, String trueValue, String falseValue) {
  833.             super(valueSeparator, new BooleanConverter(trueValue, falseValue));
  834.         }
  835.     }

  836.     /**
  837.      * Parses value to any {@link Enum}
  838.      */
  839.     public static class EnumConverter extends FromStringParameterConverter<Enum<?>> {

  840.         @Override
  841.         public boolean canConvertTo(Type type) {
  842.             return type instanceof Class<?> && ((Class<?>) type).isEnum();
  843.         }

  844.         @Override
  845.         public Enum<?> convertValue(String value, Type type) {
  846.             String typeClass = ((Class<?>) type).getName();
  847.             Class<?> enumClass = (Class<?>) type;
  848.             Method valueOfMethod = null;
  849.             try {
  850.                 valueOfMethod = enumClass.getMethod("valueOf", String.class);
  851.                 valueOfMethod.setAccessible(true);
  852.                 return (Enum<?>) valueOfMethod.invoke(enumClass, new Object[] { value });
  853.             } catch (Exception e) {
  854.                 throw new ParameterConversionFailed("Failed to convert " + value + " for Enum " + typeClass, e);
  855.             }
  856.         }
  857.     }

  858.     /**
  859.      * <p>An {@link EnumConverter} allowing stories prose to be more natural. Before performing the actual conversion,
  860.      * it ransforms values to upper-case, with any non-alphanumeric character replaced by an underscore ('_').</p>
  861.      * <p><b>Example</b>: assuming we have defined the step "{@code Given I am on the $page}" which is mapped to the
  862.      * method {@code iAmOnPage(PageEnum page)}, we can then write in a scenario:
  863.      * <pre>{@code
  864.      * Given I am on the login page
  865.      * }</pre>
  866.      * instead of:
  867.      * <pre>{@code
  868.      * Given I am on the LOGIN_PAGE
  869.      * }</pre>
  870.      * </p>
  871.      * <p><b>Warning</b>. This <i>requires</i> enum constants to follow the
  872.      * <a href="https://google-styleguide.googlecode.com/svn/trunk/javaguide.html#s5.2.4-constant-names">
  873.      * standard conventions for constant names</a>, i.e. all uppercase letters, with words separated by underscores.</p>
  874.      */
  875.     public static class FluentEnumConverter extends EnumConverter {

  876.         @Override
  877.         public Enum<?> convertValue(String value, Type type) {
  878.             return super.convertValue(value.replaceAll("\\W", "_").toUpperCase(), type);
  879.         }
  880.     }

  881.     /**
  882.      * Parses value to list of the same {@link Enum}, using an injectable value
  883.      * separator (defaults to ",") and trimming each element of the list.
  884.      */
  885.     public static class EnumListConverter extends AbstractListParameterConverter<Enum<?>> {

  886.         public EnumListConverter() {
  887.             this(DEFAULT_COLLECTION_SEPARATOR);
  888.         }

  889.         public EnumListConverter(String valueSeparator) {
  890.             super(valueSeparator, new EnumConverter());
  891.         }
  892.     }

  893.     /**
  894.      * Converts value to {@link ExamplesTable} using a
  895.      * {@link ExamplesTableFactory}.
  896.      */
  897.     public static class ExamplesTableConverter extends FunctionalParameterConverter<String, ExamplesTable> {

  898.         public ExamplesTableConverter(ExamplesTableFactory factory) {
  899.             super(factory::createExamplesTable);
  900.         }
  901.     }

  902.     /**
  903.      * Converts ExamplesTable to list of parameters, mapped to annotated custom
  904.      * types.
  905.      */
  906.     public static class ExamplesTableParametersConverter extends FromStringParameterConverter<Object> {

  907.         private final ExamplesTableFactory factory;

  908.         public ExamplesTableParametersConverter(ExamplesTableFactory factory) {
  909.             this.factory = factory;
  910.         }

  911.         @Override
  912.         public boolean canConvertTo(Type type) {
  913.             return isExamplesTableParameters(type);
  914.         }

  915.         @Override
  916.         public Object convertValue(String value, Type type) {
  917.             List<?> rows = factory.createExamplesTable(value).getRowsAs(argumentClass(type));
  918.             if (isAssignableFromRawType(List.class, type)) {
  919.                 return rows;
  920.             }
  921.             int rowCount = rows.size();
  922.             isTrue(rowCount == 1,
  923.                     "Exactly one row is expected in ExamplesTable in order to convert it to %s, but found %d row(s)",
  924.                     type, rowCount);
  925.             return rows.get(0);
  926.         }

  927.         public static boolean isExamplesTableParameters(Type type) {
  928.             return isAnnotationPresent(type, AsParameters.class);
  929.         }

  930.     }

  931.     public static class JsonConverter extends FromStringParameterConverter<Object> {

  932.         private final JsonFactory factory;

  933.         public JsonConverter() {
  934.             this(new JsonFactory());
  935.         }

  936.         public JsonConverter(final JsonFactory factory) {
  937.             this.factory = factory;
  938.         }

  939.         @Override
  940.         public boolean canConvertTo(final Type type) {
  941.             return isAnnotationPresent(type, AsJson.class);
  942.         }

  943.         @Override
  944.         public Object convertValue(final String value, final Type type) {
  945.             return factory.createJson(value, type);
  946.         }
  947.     }

  948.     public static class JsonFactory {

  949.         private Keywords keywords;
  950.         private final ResourceLoader resourceLoader;

  951.         public JsonFactory() {
  952.             this(new LocalizedKeywords());
  953.         }

  954.         public JsonFactory(final Keywords keywords) {
  955.             this(keywords, new LoadFromClasspath());
  956.         }

  957.         public JsonFactory(final ResourceLoader resourceLoader) {
  958.             this(new LocalizedKeywords(), resourceLoader);
  959.         }

  960.         public JsonFactory(final Keywords keywords, final ResourceLoader resourceLoader) {
  961.             this.keywords = keywords;
  962.             this.resourceLoader = resourceLoader;
  963.         }

  964.         public JsonFactory(final Configuration configuration) {
  965.             this.keywords = configuration.keywords();
  966.             this.resourceLoader = configuration.storyLoader();
  967.         }

  968.         public Object createJson(final String input, final Type type) {
  969.             String jsonAsString;
  970.             if (isBlank(input) || isJson(input)) {
  971.                 jsonAsString = input;
  972.             } else {
  973.                 jsonAsString = resourceLoader.loadResourceAsText(input);
  974.             }
  975.             return new Gson().fromJson(jsonAsString, type);
  976.         }

  977.         protected boolean isJson(final String input) {
  978.             return (input.startsWith("[") && input.endsWith("]")) || (input.startsWith("{") && input.endsWith("}"));
  979.         }

  980.         public void useKeywords(final Keywords keywords) {
  981.             this.keywords = keywords;
  982.         }

  983.         public Keywords keywords() {
  984.             return this.keywords;
  985.         }
  986.     }

  987.     /**
  988.      * Invokes method on instance to return value.
  989.      */
  990.     public static class MethodReturningConverter extends FromStringParameterConverter<Object> {
  991.         private Method method;
  992.         private Class<?> stepsType;
  993.         private InjectableStepsFactory stepsFactory;

  994.         public MethodReturningConverter(Method method, Object instance) {
  995.             this(method, instance.getClass(), new InstanceStepsFactory(new MostUsefulConfiguration(), instance));
  996.         }

  997.         public MethodReturningConverter(Method method, Class<?> stepsType, InjectableStepsFactory stepsFactory) {
  998.             this.method = method;
  999.             this.stepsType = stepsType;
  1000.             this.stepsFactory = stepsFactory;
  1001.         }

  1002.         @Override
  1003.         public boolean canConvertTo(Type type) {
  1004.             return isAssignableFrom(method.getReturnType(), type);
  1005.         }

  1006.         @Override
  1007.         public Object convertValue(String value, Type type) {
  1008.             try {
  1009.                 Object instance = instance();
  1010.                 return method.invoke(instance, value);
  1011.             } catch (Exception e) {
  1012.                 throw new ParameterConversionFailed("Failed to invoke method " + method + " with value " + value
  1013.                         + " in " + type, e);
  1014.             }
  1015.         }

  1016.         private Object instance() {
  1017.             return stepsFactory.createInstanceOfType(stepsType);
  1018.         }

  1019.     }

  1020. }