Coverage Report - org.jbehave.core.steps.ParameterConverters
 
Classes in this File Line Coverage Branch Coverage Complexity
ParameterConverters
100%
40/40
100%
10/10
2.266
ParameterConverters$BooleanConverter
100%
10/10
100%
2/2
2.266
ParameterConverters$BooleanListConverter
85%
17/20
62%
5/8
2.266
ParameterConverters$DateConverter
100%
12/12
75%
3/4
2.266
ParameterConverters$EnumConverter
100%
11/11
100%
2/2
2.266
ParameterConverters$EnumListConverter
94%
18/19
62%
5/8
2.266
ParameterConverters$ExamplesTableConverter
100%
9/9
100%
2/2
2.266
ParameterConverters$MethodReturningConverter
100%
18/18
100%
2/2
2.266
ParameterConverters$NumberConverter
100%
43/43
100%
40/40
2.266
ParameterConverters$NumberListConverter
100%
19/19
100%
8/8
2.266
ParameterConverters$ParameterConverter
N/A
N/A
2.266
ParameterConverters$ParameterConvertionFailed
100%
4/4
N/A
2.266
ParameterConverters$StringListConverter
100%
14/14
100%
8/8
2.266
 
 1  
 package org.jbehave.core.steps;
 2  
 
 3  
 import java.lang.reflect.Method;
 4  
 import java.lang.reflect.ParameterizedType;
 5  
 import java.lang.reflect.Type;
 6  
 import java.math.BigDecimal;
 7  
 import java.math.BigInteger;
 8  
 import java.text.DateFormat;
 9  
 import java.text.NumberFormat;
 10  
 import java.text.ParseException;
 11  
 import java.text.SimpleDateFormat;
 12  
 import java.util.ArrayList;
 13  
 import java.util.Date;
 14  
 import java.util.List;
 15  
 import java.util.Locale;
 16  
 import java.util.concurrent.CopyOnWriteArrayList;
 17  
 import java.util.concurrent.atomic.AtomicInteger;
 18  
 import java.util.concurrent.atomic.AtomicLong;
 19  
 
 20  
 import org.apache.commons.lang.BooleanUtils;
 21  
 import org.jbehave.core.configuration.MostUsefulConfiguration;
 22  
 import org.jbehave.core.model.ExamplesTable;
 23  
 import org.jbehave.core.model.ExamplesTableFactory;
 24  
 
 25  
 import static java.util.Arrays.asList;
 26  
 
 27  
 /**
 28  
  * <p>
 29  
  * Facade responsible for converting parameter values to Java objects.  It allows
 30  
  *  the registration of several {@link ParameterConverter} instances, and the first 
 31  
  *  one that is found to matches the appropriate parameter type is used.
 32  
  * </p>
 33  
  * <p>
 34  
  * Converters for several Java types are provided out-of-the-box:
 35  
  * <ul>
 36  
  * <li>{@link ParameterConverters.NumberConverter NumberConverter}</li>
 37  
  * <li>{@link ParameterConverters.NumberListConverter NumberListConverter}</li>
 38  
  * <li>{@link ParameterConverters.StringListConverter StringListConverter}</li>
 39  
  * <li>{@link ParameterConverters.DateConverter DateConverter}</li>
 40  
  * <li>{@link ParameterConverters.ExamplesTableConverter ExamplesTableConverter}</li>
 41  
  * <li>{@link ParameterConverters.MethodReturningConverter MethodReturningConverter}</li>
 42  
  * </ul>
 43  
  * </p>
 44  
  */
 45  
 public class ParameterConverters {
 46  
 
 47  1
     public static final StepMonitor DEFAULT_STEP_MONITOR = new SilentStepMonitor();
 48  1
     public static final Locale DEFAULT_NUMBER_FORMAT_LOCAL = Locale.ENGLISH;
 49  
     public static final String DEFAULT_LIST_SEPARATOR = ",";
 50  
     public static final boolean DEFAULT_THREAD_SAFETY = false;
 51  
         
 52  
     private static final String NEWLINES_PATTERN = "(\n)|(\r\n)";
 53  1
     private static final String SYSTEM_NEWLINE = System.getProperty("line.separator");
 54  
     private static final String DEFAULT_TRUE_VALUE = "true";
 55  
     private static final String DEFAULT_FALSE_VALUE = "false";
 56  
     
 57  
     private final StepMonitor monitor;
 58  
     private final List<ParameterConverter> converters;
 59  
     private final boolean threadSafe;
 60  
 
 61  
     /** 
 62  
      * Creates a non-thread-safe instance of ParameterConverters using default dependencies, 
 63  
      * a SilentStepMonitor, English as Locale and "," as list separator. 
 64  
      */
 65  
     public ParameterConverters() {
 66  1041
         this(DEFAULT_STEP_MONITOR);
 67  1041
     }
 68  
 
 69  
     /** 
 70  
      * Creates a ParameterConverters using given StepMonitor
 71  
      * 
 72  
      * @param monitor the StepMonitor to use
 73  
      */
 74  
     public ParameterConverters(StepMonitor monitor) {
 75  1041
             this(monitor, DEFAULT_NUMBER_FORMAT_LOCAL, DEFAULT_LIST_SEPARATOR, DEFAULT_THREAD_SAFETY);
 76  1041
     }
 77  
 
 78  
     /** 
 79  
      * Create a ParameterConverters with given thread-safety
 80  
      * 
 81  
      * @param threadSafe the boolean flag to determine if access to {@link ParameterConverter} should be thread-safe
 82  
      */
 83  
     public ParameterConverters(boolean threadSafe) {
 84  1
         this(DEFAULT_STEP_MONITOR, DEFAULT_NUMBER_FORMAT_LOCAL, DEFAULT_LIST_SEPARATOR, threadSafe);
 85  1
     }
 86  
 
 87  
     /** 
 88  
      * Creates a ParameterConverters for the given StepMonitor, Locale, list separator and thread-safety.  
 89  
      * When selecting a listSeparator, please make sure that this character doesn't have a special
 90  
      * meaning in your Locale (for instance "," is used as decimal separator in some Locale)
 91  
      * 
 92  
      * @param monitor the StepMonitor reporting the conversions
 93  
      * @param locale the Locale to use when reading numbers
 94  
      * @param listSeparator the String to use as list separator 
 95  
      * @param threadSafe the boolean flag to determine if modification of {@link ParameterConverter} should be thread-safe
 96  
      */
 97  
     public ParameterConverters(StepMonitor monitor, Locale locale, String listSeparator, boolean threadSafe) {
 98  1042
         this(monitor, new ArrayList<ParameterConverter>(), threadSafe);
 99  1042
         this.addConverters(defaultConverters(locale, listSeparator));            
 100  1042
     }
 101  
 
 102  1073
     private ParameterConverters(StepMonitor monitor, List<ParameterConverter> converters, boolean threadSafe) {
 103  1073
         this.monitor = monitor;
 104  1073
         this.threadSafe = threadSafe;
 105  1073
         this.converters = ( threadSafe ? new CopyOnWriteArrayList<ParameterConverter>(converters) : new ArrayList<ParameterConverter>(converters));
 106  1073
     }
 107  
 
 108  
     protected ParameterConverter[] defaultConverters(Locale locale, String listSeparator) {
 109  1042
         String escapedListSeparator = escapeRegexPunctuation(listSeparator);
 110  1042
         ParameterConverter[] defaultConverters = {
 111  
             new BooleanConverter(),
 112  
             new NumberConverter(NumberFormat.getInstance(locale)),
 113  
             new NumberListConverter(NumberFormat.getInstance(locale), escapedListSeparator), 
 114  
             new StringListConverter(escapedListSeparator),
 115  
             new DateConverter(), 
 116  
             new ExamplesTableConverter(new ExamplesTableFactory(this))
 117  
         };
 118  1042
         return defaultConverters;
 119  
     }
 120  
 
 121  
     //TODO : This is a duplicate from RegExpPrefixCapturing
 122  
     private String escapeRegexPunctuation(String matchThis) {
 123  1042
         return matchThis.replaceAll("([\\[\\]\\{\\}\\?\\^\\.\\*\\(\\)\\+\\\\])", "\\\\$1");
 124  
     }
 125  
 
 126  
     
 127  
     public ParameterConverters addConverters(ParameterConverter... converters) {
 128  3144
         return addConverters(asList(converters));
 129  
     }
 130  
 
 131  
     public ParameterConverters addConverters(List<ParameterConverter> converters) {
 132  3171
         this.converters.addAll(0, converters);
 133  3171
         return this;
 134  
     }
 135  
 
 136  
     public Object convert(String value, Type type) {
 137  
 
 138  
         // check if any converters accepts type
 139  99
         for (ParameterConverter converter : converters) {
 140  566
             if (converter.accept(type)) {
 141  42
                 Object converted = converter.convertValue(value, type);
 142  42
                 monitor.convertedValueOfType(value, type, converted, converter.getClass());
 143  42
                 return converted;
 144  
             }
 145  
         }
 146  
 
 147  57
         if (type == String.class) {
 148  55
             return replaceNewlinesWithSystemNewlines(value);
 149  
         }
 150  
 
 151  2
         throw new ParameterConvertionFailed("No parameter converter for " + type);
 152  
     }
 153  
 
 154  
     private Object replaceNewlinesWithSystemNewlines(String value) {
 155  55
         return value.replaceAll(NEWLINES_PATTERN, SYSTEM_NEWLINE);
 156  
     }
 157  
 
 158  
     public ParameterConverters newInstanceAdding(ParameterConverter converter) {
 159  31
         List<ParameterConverter> convertersForNewInstance = new ArrayList<ParameterConverter>(converters);
 160  31
         convertersForNewInstance.add(converter);
 161  31
         return new ParameterConverters(monitor, convertersForNewInstance, threadSafe);
 162  
     }
 163  
 
 164  
     public static interface ParameterConverter {
 165  
 
 166  
         boolean accept(Type type);
 167  
 
 168  
         Object convertValue(String value, Type type);
 169  
 
 170  
     }
 171  
 
 172  
     @SuppressWarnings("serial")
 173  
     public static class ParameterConvertionFailed extends RuntimeException {
 174  
 
 175  
         public ParameterConvertionFailed(String message) {
 176  2
             super(message);
 177  2
         }
 178  
 
 179  
         public ParameterConvertionFailed(String message, Throwable cause) {
 180  6
             super(message, cause);
 181  6
         }
 182  
     }
 183  
 
 184  
     /**
 185  
      * <p>
 186  
      * Converts values to numbers, supporting any subclass of {@link Number}
 187  
      * (including generic Number type), and it unboxed counterpart, using a
 188  
      * {@link NumberFormat} to parse to a {@link Number} and to convert it to a
 189  
      * specific number type:
 190  
      * <ul>
 191  
      * <li>Byte, byte: {@link Number#byteValue()}</li>
 192  
      * <li>Short, short: {@link Number#shortValue()}</li>
 193  
      * <li>Integer, int: {@link Number#intValue()}</li>
 194  
      * <li>Float, float: {@link Number#floatValue()}</li>
 195  
      * <li>Long, long: {@link Number#longValue()}</li>
 196  
      * <li>Double, double: {@link Number#doubleValue()}</li>
 197  
      * <li>BigInteger: {@link BigInteger#valueOf(Long)}</li>
 198  
      * <li>BigDecimal: {@link BigDecimal#valueOf(Double)}</li></li>
 199  
      * </ul>
 200  
      * If no number format is provided, it defaults to
 201  
      * {@link NumberFormat#getInstance(Locale.ENGLISH)}.
 202  
      * <p>
 203  
      * The localized instance {@link NumberFormat#getInstance(Locale)} can be
 204  
      * used to convert numbers in specific locales.
 205  
      * </p>
 206  
      */
 207  
     public static class NumberConverter implements ParameterConverter {
 208  
 
 209  1
         private static List<Class<?>> primitiveTypes = asList(new Class<?>[] { byte.class, short.class, int.class,
 210  
                 float.class, long.class, double.class });
 211  
 
 212  
         private final NumberFormat numberFormat;
 213  
 
 214  
         public NumberConverter() {
 215  4
             this(NumberFormat.getInstance(DEFAULT_NUMBER_FORMAT_LOCAL));
 216  4
         }
 217  
 
 218  2094
         public NumberConverter(NumberFormat numberFormat) {
 219  2094
             this.numberFormat = numberFormat;
 220  2094
         }
 221  
 
 222  
         public boolean accept(Type type) {
 223  143
             if (type instanceof Class<?>) {
 224  138
                 return Number.class.isAssignableFrom((Class<?>) type) || primitiveTypes.contains(type);
 225  
             }
 226  5
             return false;
 227  
         }
 228  
 
 229  
         public Object convertValue(String value, Type type) {
 230  
             try {
 231  136
                 Number n = numberFormat.parse(value);
 232  134
                 if (type == Byte.class || type == byte.class) {
 233  6
                     return n.byteValue();
 234  128
                 } else if (type == Short.class || type == short.class) {
 235  6
                     return n.shortValue();
 236  122
                 } else if (type == Integer.class || type == int.class) {
 237  29
                     return n.intValue();
 238  93
                 } else if (type == Float.class || type == float.class) {
 239  21
                     return n.floatValue();
 240  72
                 } else if (type == Long.class || type == long.class) {
 241  15
                     return n.longValue();
 242  57
                 } else if (type == Double.class || type == double.class) {
 243  22
                     return n.doubleValue();
 244  35
                 } else if (type == BigInteger.class) {
 245  3
                     return BigInteger.valueOf(n.longValue());
 246  32
                 } else if (type == BigDecimal.class) {
 247  13
                     return new BigDecimal(canonicalize(value));
 248  19
                 } else if (type == AtomicInteger.class) {
 249  4
                     return new AtomicInteger(Integer.parseInt(value));
 250  15
                 } else if (type == AtomicLong.class) {
 251  3
                     return new AtomicLong(Long.parseLong(value));
 252  
                 } else {
 253  12
                     return n;
 254  
                 }
 255  1
             } catch (NumberFormatException e) {
 256  1
                 throw new ParameterConvertionFailed(value, e);
 257  2
             } catch (ParseException e) {
 258  2
                 throw new ParameterConvertionFailed(value, e);
 259  
             }
 260  
         }
 261  
 
 262  
         private String canonicalize(String value) {
 263  13
             char decimalPointSeparator = numberFormat.format(1.01).charAt(1);
 264  13
             int decimalPointPosition = value.lastIndexOf(decimalPointSeparator);
 265  13
             value = value.trim();
 266  13
             if (decimalPointPosition != -1) {
 267  10
                 String sf = value.substring(0, decimalPointPosition).replace("[^0-9]", "");
 268  10
                 String dp = value.substring(decimalPointPosition+1);
 269  10
                 return sf + "." + dp;
 270  
             }
 271  3
             return value.replace(' ', ',');
 272  
         }
 273  
 
 274  
     }
 275  
 
 276  
     /**
 277  
      * Converts value to list of numbers. Splits value to a list, using an
 278  
      * injectable value separator (defaulting to ",") and converts each element
 279  
      * of list via the {@link NumberConverter}, using the {@link NumberFormat}
 280  
      * provided (defaulting to {@link NumberFormat#getInstance(Locale.ENGLISH)}).
 281  
      */
 282  
     public static class NumberListConverter implements ParameterConverter {
 283  
 
 284  
         private final NumberConverter numberConverter;
 285  
         private final String valueSeparator;
 286  
 
 287  
         public NumberListConverter() {
 288  2
             this(NumberFormat.getInstance(DEFAULT_NUMBER_FORMAT_LOCAL), DEFAULT_LIST_SEPARATOR);
 289  2
         }
 290  
 
 291  
         /**
 292  
          * @param numberFormat Specific NumberFormat to use.
 293  
          * @param valueSeparator A regexp to use as list separate
 294  
          */
 295  1046
         public NumberListConverter(NumberFormat numberFormat, String valueSeparator) {
 296  1046
             this.numberConverter = new NumberConverter(numberFormat);
 297  1046
             this.valueSeparator = valueSeparator;
 298  1046
         }
 299  
 
 300  
         public boolean accept(Type type) {
 301  70
             if (type instanceof ParameterizedType) {
 302  7
                 Type rawType = rawType(type);
 303  7
                 Type argumentType = argumentType(type);
 304  7
                 return List.class.isAssignableFrom((Class<?>) rawType)
 305  
                         && Number.class.isAssignableFrom((Class<?>) argumentType);
 306  
             }
 307  63
             return false;
 308  
         }
 309  
 
 310  
         private Type rawType(Type type) {
 311  7
             return ((ParameterizedType) type).getRawType();
 312  
         }
 313  
 
 314  
         private Type argumentType(Type type) {
 315  18
             return ((ParameterizedType) type).getActualTypeArguments()[0];
 316  
         }
 317  
 
 318  
         @SuppressWarnings("unchecked")
 319  
         public Object convertValue(String value, Type type) {
 320  11
             Class<? extends Number> argumentType = (Class<? extends Number>) argumentType(type);
 321  11
             List<String> values = trim(asList(value.split(valueSeparator)));
 322  11
             List<Number> numbers = new ArrayList<Number>();
 323  11
             for (String numberValue : values) {
 324  40
                 numbers.add((Number) numberConverter.convertValue(numberValue, argumentType));
 325  
             }
 326  10
             return numbers;
 327  
         }
 328  
 
 329  
     }
 330  
 
 331  
     /**
 332  
      * Converts value to list of String. Splits value to a list, using an
 333  
      * injectable value separator (defaults to ",") and trimming each element of
 334  
      * the list.
 335  
      */
 336  
     public static class StringListConverter implements ParameterConverter {
 337  
 
 338  
         private String valueSeparator;
 339  
 
 340  
         public StringListConverter() {
 341  1
             this(DEFAULT_LIST_SEPARATOR);
 342  1
         }
 343  
 
 344  
         /**
 345  
          * @param numberFormat Specific NumberFormat to use.
 346  
          * @param valueSeparator A regexp to use as list separate
 347  
          */
 348  1043
         public StringListConverter(String valueSeparator) {
 349  1043
             this.valueSeparator = valueSeparator;
 350  1043
         }
 351  
 
 352  
         public boolean accept(Type type) {
 353  67
             if (type instanceof ParameterizedType) {
 354  4
                 ParameterizedType parameterizedType = (ParameterizedType) type;
 355  4
                 Type rawType = parameterizedType.getRawType();
 356  4
                 Type argumentType = parameterizedType.getActualTypeArguments()[0];
 357  4
                 return List.class.isAssignableFrom((Class<?>) rawType)
 358  
                         && String.class.isAssignableFrom((Class<?>) argumentType);
 359  
             }
 360  63
             return false;
 361  
         }
 362  
 
 363  
         public Object convertValue(String value, Type type) {
 364  3
             if (value.trim().length() == 0)
 365  1
                 return asList();
 366  2
             return trim(asList(value.split(valueSeparator)));
 367  
         }
 368  
 
 369  
     }
 370  
 
 371  
     public static List<String> trim(List<String> values) {
 372  15
         List<String> trimmed = new ArrayList<String>();
 373  15
         for (String value : values) {
 374  51
             trimmed.add(value.trim());
 375  
         }
 376  15
         return trimmed;
 377  
     }
 378  
 
 379  
     /**
 380  
      * Parses value to a {@link Date} using an injectable {@link DateFormat}
 381  
      * (defaults to <b>new SimpleDateFormat("dd/MM/yyyy")</b>)
 382  
      */
 383  
     public static class DateConverter implements ParameterConverter {
 384  
 
 385  1
         public static final DateFormat DEFAULT_FORMAT = new SimpleDateFormat("dd/MM/yyyy");
 386  
 
 387  
         private final DateFormat dateFormat;
 388  
 
 389  
         public DateConverter() {
 390  1044
             this(DEFAULT_FORMAT);
 391  1044
         }
 392  
 
 393  1045
         public DateConverter(DateFormat dateFormat) {
 394  1045
             this.dateFormat = dateFormat;
 395  1045
         }
 396  
 
 397  
         public boolean accept(Type type) {
 398  69
             if (type instanceof Class<?>) {
 399  67
                 return Date.class.isAssignableFrom((Class<?>) type);
 400  
             }
 401  2
             return false;
 402  
         }
 403  
 
 404  
         public Object convertValue(String value, Type type) {
 405  
             try {
 406  3
                 return dateFormat.parse(value);
 407  1
             } catch (ParseException e) {
 408  1
                 throw new ParameterConvertionFailed("Failed to convert value " + value + " with date format "
 409  
                         + (dateFormat instanceof SimpleDateFormat ? ((SimpleDateFormat) dateFormat).toPattern() : dateFormat), e);
 410  
             }
 411  
         }
 412  
 
 413  
     }
 414  
 
 415  
     public static class BooleanConverter implements ParameterConverter {
 416  
         private String trueValue;
 417  
         private String falseValue;
 418  
 
 419  
         public BooleanConverter() {
 420  1043
             this(DEFAULT_TRUE_VALUE, DEFAULT_FALSE_VALUE);
 421  1043
         }
 422  
 
 423  1044
         public BooleanConverter(String trueValue, String falseValue) {
 424  1044
             this.trueValue = trueValue;
 425  1044
             this.falseValue = falseValue;
 426  1044
         }
 427  
 
 428  
         public boolean accept(Type type) {
 429  99
             if (type instanceof Class<?>) {
 430  93
                 return Boolean.class.isAssignableFrom((Class<?>) type);
 431  
             }
 432  6
             return false;
 433  
         }
 434  
 
 435  
         public Object convertValue(String value, Type type) {
 436  11
             return BooleanUtils.toBoolean(value, trueValue, falseValue);
 437  
         }
 438  
     }
 439  
 
 440  
     public static class BooleanListConverter implements ParameterConverter {
 441  
         private final BooleanConverter booleanConverter;
 442  
         private String valueSeparator;
 443  
 
 444  
         public BooleanListConverter() {
 445  1
             this(DEFAULT_LIST_SEPARATOR, DEFAULT_TRUE_VALUE, DEFAULT_FALSE_VALUE);
 446  1
         }
 447  
 
 448  
         public BooleanListConverter(String valueSeparator) {
 449  0
             this(valueSeparator, DEFAULT_TRUE_VALUE, DEFAULT_FALSE_VALUE);
 450  0
         }
 451  
 
 452  1
         public BooleanListConverter(String valueSeparator, String trueValue, String falseValue) {
 453  1
             this.valueSeparator = valueSeparator;
 454  1
             booleanConverter = new BooleanConverter(trueValue, falseValue);
 455  1
         }
 456  
 
 457  
         public boolean accept(Type type) {
 458  1
             if (type instanceof ParameterizedType) {
 459  1
                 Type rawType = rawType(type);
 460  1
                 Type argumentType = argumentType(type);
 461  1
                 return List.class.isAssignableFrom((Class<?>) rawType)
 462  
                         && Boolean.class.isAssignableFrom((Class<?>) argumentType);
 463  
             }
 464  0
             return false;
 465  
         }
 466  
 
 467  
         public Object convertValue(String value, Type type) {
 468  1
             List<String> values = trim(asList(value.split(valueSeparator)));
 469  1
             List<Boolean> booleans = new ArrayList<Boolean>();
 470  
 
 471  1
             for (String booleanValue : values) {
 472  3
                 booleans.add((Boolean) booleanConverter.convertValue(booleanValue, type));
 473  
             }
 474  1
             return booleans;
 475  
         }
 476  
 
 477  
         private Type rawType(Type type) {
 478  1
             return ((ParameterizedType) type).getRawType();
 479  
         }
 480  
 
 481  
         private Type argumentType(Type type) {
 482  1
             return ((ParameterizedType) type).getActualTypeArguments()[0];
 483  
         }
 484  
     }
 485  
 
 486  
     /**
 487  
      * Parses value to any {@link Enum}
 488  
      */
 489  3
     public static class EnumConverter implements ParameterConverter {
 490  
 
 491  
         public boolean accept(Type type) {
 492  4
             if (type instanceof Class<?>) {
 493  3
                 return ((Class<?>) type).isEnum();
 494  
             }
 495  1
             return false;
 496  
         }
 497  
 
 498  
         public Object convertValue(String value, Type type) {
 499  5
             String typeClass = ((Class<?>) type).getName();
 500  5
             Class<?> enumClass = (Class<?>) type;
 501  5
             Method valueOfMethod = null;
 502  
             try {
 503  5
                 valueOfMethod = enumClass.getMethod("valueOf", new Class[] { String.class });
 504  5
                 return valueOfMethod.invoke(enumClass, new Object[] { value });
 505  1
             } catch (Exception e) {
 506  1
                 throw new ParameterConvertionFailed("Failed to convert " + value + " for Enum " + typeClass, e);
 507  
             }
 508  
         }
 509  
     }
 510  
  
 511  
     /**
 512  
      * Parses value to list of the same {@link Enum}, using an injectable
 513  
      * value separator (defaults to ",") and trimming each element of the
 514  
      * list.
 515  
      */
 516  
     public static class EnumListConverter implements ParameterConverter {
 517  
         private final EnumConverter enumConverter;
 518  
         private String valueSeparator;
 519  
 
 520  
         public EnumListConverter() {
 521  1
             this(DEFAULT_LIST_SEPARATOR);
 522  1
         }
 523  
         
 524  1
         public EnumListConverter(String valueSeparator) {
 525  1
             this.enumConverter = new EnumConverter();
 526  1
             this.valueSeparator = valueSeparator;
 527  1
         }
 528  
 
 529  
         public boolean accept(Type type) {
 530  1
             if (type instanceof ParameterizedType) {
 531  1
                 Type rawType = rawType(type);
 532  1
                 Type argumentType = argumentType(type);
 533  1
                 return List.class.isAssignableFrom((Class<?>) rawType)
 534  
                         && enumConverter.accept(argumentType);
 535  
             }
 536  0
             return false;
 537  
         }
 538  
 
 539  
         public Object convertValue(String value, Type type) {
 540  1
             Type argumentType = argumentType(type);
 541  1
             List<String> values = trim(asList(value.split(valueSeparator)));
 542  1
             List<Enum<?>> enums = new ArrayList<Enum<?>>();
 543  1
             for (String string : values) {
 544  3
                 enums.add((Enum<?>) enumConverter.convertValue(string, argumentType));
 545  
             }
 546  1
             return enums;
 547  
         }
 548  
 
 549  
         private Type rawType(Type type) {
 550  1
             return ((ParameterizedType) type).getRawType();
 551  
         }
 552  
 
 553  
         private Type argumentType(Type type) {
 554  2
             return ((ParameterizedType) type).getActualTypeArguments()[0];
 555  
         }
 556  
     }
 557  
     
 558  
     /**
 559  
      * Converts value to {@link ExamplesTable} using a {@link ExamplesTableFactory}.
 560  
      */
 561  
     public static class ExamplesTableConverter implements ParameterConverter {
 562  
 
 563  
         private final ExamplesTableFactory factory;
 564  
         
 565  
         public ExamplesTableConverter(){
 566  1
             this(new ExamplesTableFactory());           
 567  1
         }
 568  
 
 569  1043
         public ExamplesTableConverter(ExamplesTableFactory factory){
 570  1043
             this.factory = factory;            
 571  1043
         }
 572  
 
 573  
         public boolean accept(Type type) {
 574  66
             if (type instanceof Class<?>) {
 575  65
                 return ExamplesTable.class.isAssignableFrom((Class<?>) type);
 576  
             }
 577  1
             return false;
 578  
         }
 579  
 
 580  
         public Object convertValue(String value, Type type) {
 581  1
             return factory.createExamplesTable(value);
 582  
         }
 583  
 
 584  
     }
 585  
 
 586  
     /**
 587  
      * Invokes method on instance to return value.
 588  
      */
 589  
     public static class MethodReturningConverter implements ParameterConverter {
 590  
         private Method method;
 591  
         private Class<?> stepsType;
 592  
         private InjectableStepsFactory stepsFactory;
 593  
 
 594  4
         public MethodReturningConverter(Method method, Object instance) {
 595  4
             this.method = method;
 596  4
             this.stepsType = instance.getClass();
 597  4
             this.stepsFactory = new InstanceStepsFactory(new MostUsefulConfiguration(), instance);
 598  4
         }
 599  
 
 600  1
         public MethodReturningConverter(Method method, Class<?> stepsType, InjectableStepsFactory stepsFactory) {
 601  1
             this.method = method;
 602  1
             this.stepsType = stepsType;
 603  1
             this.stepsFactory = stepsFactory;
 604  1
         }
 605  
 
 606  
         public boolean accept(Type type) {
 607  16
             if (type instanceof Class<?>) {
 608  15
                 return method.getReturnType().isAssignableFrom((Class<?>) type);
 609  
             }
 610  1
             return false;
 611  
         }
 612  
 
 613  
         public Object convertValue(String value, Type type) {
 614  
             try {
 615  5
                 Object instance = instance();
 616  5
                 return method.invoke(instance, value);
 617  1
             } catch (Exception e) {
 618  1
                 throw new ParameterConvertionFailed("Failed to invoke method " + method + " with value " + value
 619  
                         + " in " + type, e);
 620  
             }
 621  
         }
 622  
 
 623  
         private Object instance() {
 624  5
             return stepsFactory.createInstanceOfType(stepsType);
 625  
         }
 626  
 
 627  
     }
 628  
 }