Coverage Report - org.jbehave.core.steps.ParameterConverters
 
Classes in this File Line Coverage Branch Coverage Complexity
ParameterConverters
100%
45/45
90%
9/10
2.333
ParameterConverters$BooleanConverter
100%
12/12
100%
6/6
2.333
ParameterConverters$BooleanListConverter
86%
19/22
62%
5/8
2.333
ParameterConverters$DateConverter
100%
13/13
75%
3/4
2.333
ParameterConverters$EnumConverter
100%
12/12
100%
2/2
2.333
ParameterConverters$EnumListConverter
100%
20/20
75%
6/8
2.333
ParameterConverters$ExamplesTableConverter
100%
9/9
100%
2/2
2.333
ParameterConverters$ExamplesTableParametersConverter
95%
21/22
83%
10/12
2.333
ParameterConverters$FluentEnumConverter
100%
2/2
N/A
2.333
ParameterConverters$MethodReturningConverter
100%
18/18
100%
2/2
2.333
ParameterConverters$NumberConverter
95%
63/66
93%
45/48
2.333
ParameterConverters$NumberListConverter
100%
21/21
100%
8/8
2.333
ParameterConverters$ParameterConverter
N/A
N/A
2.333
ParameterConverters$ParameterConvertionFailed
100%
4/4
N/A
2.333
ParameterConverters$StringListConverter
100%
15/15
100%
8/8
2.333
 
 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.DecimalFormat;
 10  
 import java.text.DecimalFormatSymbols;
 11  
 import java.text.NumberFormat;
 12  
 import java.text.ParseException;
 13  
 import java.text.SimpleDateFormat;
 14  
 import java.util.ArrayList;
 15  
 import java.util.Date;
 16  
 import java.util.List;
 17  
 import java.util.Locale;
 18  
 import java.util.concurrent.CopyOnWriteArrayList;
 19  
 import java.util.concurrent.atomic.AtomicInteger;
 20  
 import java.util.concurrent.atomic.AtomicLong;
 21  
 
 22  
 import org.apache.commons.lang.BooleanUtils;
 23  
 import org.jbehave.core.annotations.AsParameters;
 24  
 import org.jbehave.core.configuration.MostUsefulConfiguration;
 25  
 import org.jbehave.core.model.ExamplesTable;
 26  
 import org.jbehave.core.model.ExamplesTableFactory;
 27  
 
 28  
 import static java.util.Arrays.asList;
 29  
 
 30  
 /**
 31  
  * <p>
 32  
  * Facade responsible for converting parameter values to Java objects. It allows
 33  
  * the registration of several {@link ParameterConverter} instances, and the
 34  
  * first one that is found to matches the appropriate parameter type is used.
 35  
  * </p>
 36  
  * <p>
 37  
  * Converters for several Java types are provided out-of-the-box:
 38  
  * <ul>
 39  
  * <li>{@link ParameterConverters.NumberConverter NumberConverter}</li>
 40  
  * <li>{@link ParameterConverters.NumberListConverter NumberListConverter}</li>
 41  
  * <li>{@link ParameterConverters.StringListConverter StringListConverter}</li>
 42  
  * <li>{@link ParameterConverters.DateConverter DateConverter}</li>
 43  
  * <li>{@link ParameterConverters.ExamplesTableConverter ExamplesTableConverter}
 44  
  * </li>
 45  
  * <li>{@link ParameterConverters.ExamplesTableParametersConverter
 46  
  * ExamplesTableParametersConverter}</li>
 47  
  * <li>{@link ParameterConverters.MethodReturningConverter
 48  
  * MethodReturningConverter}</li>
 49  
  * </ul>
 50  
  * </p>
 51  
  */
 52  
 public class ParameterConverters {
 53  
 
 54  1
     public static final StepMonitor DEFAULT_STEP_MONITOR = new SilentStepMonitor();
 55  1
     public static final Locale DEFAULT_NUMBER_FORMAT_LOCAL = Locale.ENGLISH;
 56  
     public static final String DEFAULT_LIST_SEPARATOR = ",";
 57  
     public static final boolean DEFAULT_THREAD_SAFETY = true;
 58  
 
 59  
     private static final String NEWLINES_PATTERN = "(\n)|(\r\n)";
 60  1
     private static final String SYSTEM_NEWLINE = System.getProperty("line.separator");
 61  
     private static final String DEFAULT_TRUE_VALUE = "true";
 62  
     private static final String DEFAULT_FALSE_VALUE = "false";
 63  
 
 64  
     private final StepMonitor monitor;
 65  
     private final List<ParameterConverter> converters;
 66  
     private final boolean threadSafe;
 67  
 
 68  
     /**
 69  
      * Creates a non-thread-safe instance of ParameterConverters using default
 70  
      * dependencies, a SilentStepMonitor, English as Locale and "," as list
 71  
      * separator.
 72  
      */
 73  
     public ParameterConverters() {
 74  537
         this(DEFAULT_STEP_MONITOR);
 75  537
     }
 76  
 
 77  
     /**
 78  
      * Creates a ParameterConverters using given StepMonitor
 79  
      * 
 80  
      * @param monitor the StepMonitor to use
 81  
      */
 82  
     public ParameterConverters(StepMonitor monitor) {
 83  537
         this(monitor, DEFAULT_NUMBER_FORMAT_LOCAL, DEFAULT_LIST_SEPARATOR, DEFAULT_THREAD_SAFETY);
 84  537
     }
 85  
 
 86  
     /**
 87  
      * Create a ParameterConverters with given thread-safety
 88  
      * 
 89  
      * @param threadSafe the boolean flag to determine if access to
 90  
      *            {@link ParameterConverter} should be thread-safe
 91  
      */
 92  
     public ParameterConverters(boolean threadSafe) {
 93  1
         this(DEFAULT_STEP_MONITOR, DEFAULT_NUMBER_FORMAT_LOCAL, DEFAULT_LIST_SEPARATOR, threadSafe);
 94  1
     }
 95  
 
 96  
     /**
 97  
      * Creates a ParameterConverters for the given StepMonitor, Locale, list
 98  
      * separator and thread-safety. When selecting a listSeparator, please make
 99  
      * sure that this character doesn't have a special meaning in your Locale
 100  
      * (for instance "," is used as decimal separator in some Locale)
 101  
      * 
 102  
      * @param monitor the StepMonitor reporting the conversions
 103  
      * @param locale the Locale to use when reading numbers
 104  
      * @param listSeparator the String to use as list separator
 105  
      * @param threadSafe the boolean flag to determine if modification of
 106  
      *            {@link ParameterConverter} should be thread-safe
 107  
      */
 108  
     public ParameterConverters(StepMonitor monitor, Locale locale, String listSeparator, boolean threadSafe) {
 109  538
         this(monitor, new ArrayList<ParameterConverter>(), threadSafe);
 110  538
         this.addConverters(defaultConverters(locale, listSeparator));
 111  538
     }
 112  
 
 113  571
     private ParameterConverters(StepMonitor monitor, List<ParameterConverter> converters, boolean threadSafe) {
 114  571
         this.monitor = monitor;
 115  571
         this.threadSafe = threadSafe;
 116  571
         this.converters = (threadSafe ? new CopyOnWriteArrayList<ParameterConverter>(converters)
 117  
                 : new ArrayList<ParameterConverter>(converters));
 118  571
     }
 119  
 
 120  
     protected ParameterConverter[] defaultConverters(Locale locale, String listSeparator) {
 121  539
         String escapedListSeparator = escapeRegexPunctuation(listSeparator);
 122  539
         ExamplesTableFactory tableFactory = new ExamplesTableFactory(this);
 123  539
         ParameterConverter[] defaultConverters = { new BooleanConverter(),
 124  539
                 new NumberConverter(NumberFormat.getInstance(locale)),
 125  539
                 new NumberListConverter(NumberFormat.getInstance(locale), escapedListSeparator),
 126  
                 new StringListConverter(escapedListSeparator), new DateConverter(),
 127  
                 new EnumConverter(), new EnumListConverter(),
 128  
                 new ExamplesTableConverter(tableFactory), new ExamplesTableParametersConverter(tableFactory) };
 129  539
         return defaultConverters;
 130  
     }
 131  
 
 132  
     // TODO : This is a duplicate from RegExpPrefixCapturing
 133  
     private String escapeRegexPunctuation(String matchThis) {
 134  539
         return matchThis.replaceAll("([\\[\\]\\{\\}\\?\\^\\.\\*\\(\\)\\+\\\\])", "\\\\$1");
 135  
     }
 136  
 
 137  
     public ParameterConverters addConverters(ParameterConverter... converters) {
 138  540
         return addConverters(asList(converters));
 139  
     }
 140  
 
 141  
     public ParameterConverters addConverters(List<ParameterConverter> converters) {
 142  622
         this.converters.addAll(0, converters);
 143  622
         return this;
 144  
     }
 145  
 
 146  
     public Object convert(String value, Type type) {
 147  
 
 148  
         // check if any converters accepts type
 149  197
         for (ParameterConverter converter : converters) {
 150  1253
             if (converter.accept(type)) {
 151  83
                 Object converted = converter.convertValue(value, type);
 152  83
                 monitor.convertedValueOfType(value, type, converted, converter.getClass());
 153  83
                 return converted;
 154  
             }
 155  1170
         }
 156  
 
 157  114
         if (type == String.class) {
 158  112
             return replaceNewlinesWithSystemNewlines(value);
 159  
         }
 160  
 
 161  2
         throw new ParameterConvertionFailed("No parameter converter for " + type);
 162  
     }
 163  
 
 164  
     private Object replaceNewlinesWithSystemNewlines(String value) {
 165  112
         return value.replaceAll(NEWLINES_PATTERN, SYSTEM_NEWLINE);
 166  
     }
 167  
 
 168  
     public ParameterConverters newInstanceAdding(ParameterConverter converter) {
 169  33
         List<ParameterConverter> convertersForNewInstance = new ArrayList<ParameterConverter>(converters);
 170  33
         convertersForNewInstance.add(converter);
 171  33
         return new ParameterConverters(monitor, convertersForNewInstance, threadSafe);
 172  
     }
 173  
 
 174  
     public static interface ParameterConverter {
 175  
 
 176  
         boolean accept(Type type);
 177  
 
 178  
         Object convertValue(String value, Type type);
 179  
 
 180  
     }
 181  
 
 182  
     @SuppressWarnings("serial")
 183  
     public static class ParameterConvertionFailed extends RuntimeException {
 184  
 
 185  
         public ParameterConvertionFailed(String message) {
 186  2
             super(message);
 187  2
         }
 188  
 
 189  
         public ParameterConvertionFailed(String message, Throwable cause) {
 190  5
             super(message, cause);
 191  5
         }
 192  
     }
 193  
 
 194  
     /**
 195  
      * <p>
 196  
      * Converts values to numbers, supporting any subclass of {@link Number}
 197  
      * (including generic Number type), and it unboxed counterpart, using a
 198  
      * {@link NumberFormat} to parse to a {@link Number} and to convert it to a
 199  
      * specific number type:
 200  
      * <ul>
 201  
      * <li>Byte, byte: {@link Number#byteValue()}</li>
 202  
      * <li>Short, short: {@link Number#shortValue()}</li>
 203  
      * <li>Integer, int: {@link Number#intValue()}</li>
 204  
      * <li>Float, float: {@link Number#floatValue()}</li>
 205  
      * <li>Long, long: {@link Number#longValue()}</li>
 206  
      * <li>Double, double: {@link Number#doubleValue()}</li>
 207  
      * <li>BigInteger: {@link BigInteger#valueOf(Long)}</li>
 208  
      * <li>BigDecimal: {@link BigDecimal#valueOf(Double)}</li></li>
 209  
      * </ul>
 210  
      * If no number format is provided, it defaults to
 211  
      * {@link NumberFormat#getInstance(Locale.ENGLISH)}.
 212  
      * <p>
 213  
      * The localized instance {@link NumberFormat#getInstance(Locale)} can be
 214  
      * used to convert numbers in specific locales.
 215  
      * </p>
 216  
      */
 217  
     public static class NumberConverter implements ParameterConverter {
 218  1
         private static List<Class<?>> primitiveTypes = asList(new Class<?>[] { byte.class, short.class, int.class,
 219  
                 float.class, long.class, double.class });
 220  
 
 221  
         private final NumberFormat numberFormat;
 222  1090
         private ThreadLocal<NumberFormat> threadLocalNumberFormat = new ThreadLocal<NumberFormat>();
 223  
 
 224  
         public NumberConverter() {
 225  4
             this(NumberFormat.getInstance(DEFAULT_NUMBER_FORMAT_LOCAL));
 226  4
         }
 227  
 
 228  1090
         public NumberConverter(NumberFormat numberFormat) {
 229  1090
             synchronized (this) {
 230  1090
                 this.numberFormat = numberFormat;
 231  1090
                 this.threadLocalNumberFormat.set((NumberFormat) this.numberFormat.clone());
 232  1090
             }
 233  1090
         }
 234  
 
 235  
         public boolean accept(Type type) {
 236  312
             if (type instanceof Class<?>) {
 237  307
                 return Number.class.isAssignableFrom((Class<?>) type) || primitiveTypes.contains(type);
 238  
             }
 239  5
             return false;
 240  
         }
 241  
 
 242  
         public Object convertValue(String value, Type type) {
 243  
             try {
 244  272
                 Number n = numberFormat().parse(value);
 245  270
                 if (type == Byte.class || type == byte.class) {
 246  14
                     return n.byteValue();
 247  256
                 } else if (type == Short.class || type == short.class) {
 248  14
                     return n.shortValue();
 249  242
                 } else if (type == Integer.class || type == int.class) {
 250  43
                     return n.intValue();
 251  199
                 } else if (type == Float.class || type == float.class) {
 252  32
                     return n.floatValue();
 253  166
                 } else if (type == Long.class || type == long.class) {
 254  26
                     return n.longValue();
 255  141
                 } else if (type == Double.class || type == double.class) {
 256  36
                     return n.doubleValue();
 257  105
                 } else if (type == BigInteger.class) {
 258  7
                     return BigInteger.valueOf(n.longValue());
 259  98
                 } else if (type == BigDecimal.class) {
 260  30
                     return new BigDecimal(canonicalize(value));
 261  68
                 } else if (type == AtomicInteger.class) {
 262  45
                     return new AtomicInteger(Integer.parseInt(value));
 263  23
                 } else if (type == AtomicLong.class) {
 264  7
                     return new AtomicLong(Long.parseLong(value));
 265  
                 } else {
 266  16
                     return n;
 267  
                 }
 268  0
             } catch (NumberFormatException e) {
 269  0
                 throw new ParameterConvertionFailed(value, e);
 270  2
             } catch (ParseException e) {
 271  2
                 throw new ParameterConvertionFailed(value, e);
 272  
             }
 273  
         }
 274  
 
 275  
         /**
 276  
          * Return NumberFormat instance with preferred locale threadsafe
 277  
          * 
 278  
          * @return A threadlocal version of original NumberFormat instance
 279  
          */
 280  
         private NumberFormat numberFormat() {
 281  331
             if (threadLocalNumberFormat.get() == null) {
 282  19
                 synchronized (this) {
 283  19
                     threadLocalNumberFormat.set((NumberFormat) numberFormat.clone());
 284  19
                 }
 285  
             }
 286  332
             return threadLocalNumberFormat.get();
 287  
         }
 288  
 
 289  
         /**
 290  
          * Canonicalize a number representation to a format suitable for the
 291  
          * {@link BigDecimal(String)} constructor, taking into account the
 292  
          * settings of the currently configured DecimalFormat.
 293  
          * 
 294  
          * @param value a localized number value
 295  
          * @return A canonicalized string value suitable for consumption by
 296  
          *         BigDecimal
 297  
          */
 298  
         private String canonicalize(String value) {
 299  30
             char decimalPointSeparator = '.'; // default
 300  30
             char minusSign = '-'; // default
 301  30
             String rxNotDigits = "[^0-9]";
 302  30
             StringBuilder builder = new StringBuilder(value.length());
 303  
 
 304  
             // override defaults according to numberFormat's settings
 305  30
             if (numberFormat() instanceof DecimalFormat) {
 306  30
                 DecimalFormatSymbols decimalFormatSymbols = ((DecimalFormat) numberFormat()).getDecimalFormatSymbols();
 307  30
                 minusSign = decimalFormatSymbols.getMinusSign();
 308  30
                 decimalPointSeparator = decimalFormatSymbols.getDecimalSeparator();
 309  
             }
 310  
 
 311  30
             value = value.trim();
 312  30
             int decimalPointPosition = value.lastIndexOf(decimalPointSeparator);
 313  30
             boolean isNegative = value.charAt(0) == minusSign;
 314  
 
 315  30
             if (isNegative) {
 316  0
                 builder.append('-'); // fixed "-" for BigDecimal constructur
 317  
             }
 318  
 
 319  30
             if (decimalPointPosition != -1) {
 320  23
                 String sf = value.substring(0, decimalPointPosition).replaceAll(rxNotDigits, "");
 321  23
                 String dp = value.substring(decimalPointPosition + 1).replaceAll(rxNotDigits, "");
 322  
 
 323  23
                 builder.append(sf);
 324  23
                 builder.append('.'); // fixed "." for BigDecimal constructor
 325  23
                 builder.append(dp);
 326  
 
 327  23
             } else {
 328  7
                 builder.append(value.replaceAll(rxNotDigits, ""));
 329  
             }
 330  30
             return builder.toString();
 331  
         }
 332  
     }
 333  
 
 334  
     /**
 335  
      * Converts value to list of numbers. Splits value to a list, using an
 336  
      * injectable value separator (defaulting to ",") and converts each element
 337  
      * of list via the {@link NumberConverter}, using the {@link NumberFormat}
 338  
      * provided (defaulting to {@link NumberFormat#getInstance(Locale.ENGLISH)}
 339  
      * ).
 340  
      */
 341  
     public static class NumberListConverter implements ParameterConverter {
 342  
 
 343  
         private final NumberConverter numberConverter;
 344  
         private final String valueSeparator;
 345  
 
 346  
         public NumberListConverter() {
 347  2
             this(NumberFormat.getInstance(DEFAULT_NUMBER_FORMAT_LOCAL), DEFAULT_LIST_SEPARATOR);
 348  2
         }
 349  
 
 350  
         /**
 351  
          * @param numberFormat Specific NumberFormat to use.
 352  
          * @param valueSeparator A regexp to use as list separate
 353  
          */
 354  543
         public NumberListConverter(NumberFormat numberFormat, String valueSeparator) {
 355  543
             this.numberConverter = new NumberConverter(numberFormat);
 356  543
             this.valueSeparator = valueSeparator;
 357  543
         }
 358  
 
 359  
         public boolean accept(Type type) {
 360  127
             if (type instanceof ParameterizedType) {
 361  7
                 Type rawType = rawType(type);
 362  7
                 Type argumentType = argumentType(type);
 363  7
                 return List.class.isAssignableFrom((Class<?>) rawType)
 364  6
                         && Number.class.isAssignableFrom((Class<?>) argumentType);
 365  
             }
 366  120
             return false;
 367  
         }
 368  
 
 369  
         private Type rawType(Type type) {
 370  7
             return ((ParameterizedType) type).getRawType();
 371  
         }
 372  
 
 373  
         private Type argumentType(Type type) {
 374  18
             return ((ParameterizedType) type).getActualTypeArguments()[0];
 375  
         }
 376  
 
 377  
         @SuppressWarnings("unchecked")
 378  
         public Object convertValue(String value, Type type) {
 379  11
             Class<? extends Number> argumentType = (Class<? extends Number>) argumentType(type);
 380  11
             List<String> values = trim(asList(value.split(valueSeparator)));
 381  11
             List<Number> numbers = new ArrayList<Number>();
 382  11
             for (String numberValue : values) {
 383  40
                 numbers.add((Number) numberConverter.convertValue(numberValue, argumentType));
 384  39
             }
 385  10
             return numbers;
 386  
         }
 387  
 
 388  
     }
 389  
 
 390  
     /**
 391  
      * Converts value to list of String. Splits value to a list, using an
 392  
      * injectable value separator (defaults to ",") and trimming each element of
 393  
      * the list.
 394  
      */
 395  
     public static class StringListConverter implements ParameterConverter {
 396  
 
 397  
         private String valueSeparator;
 398  
 
 399  
         public StringListConverter() {
 400  1
             this(DEFAULT_LIST_SEPARATOR);
 401  1
         }
 402  
 
 403  
         /**
 404  
          * @param numberFormat Specific NumberFormat to use.
 405  
          * @param valueSeparator A regexp to use as list separate
 406  
          */
 407  540
         public StringListConverter(String valueSeparator) {
 408  540
             this.valueSeparator = valueSeparator;
 409  540
         }
 410  
 
 411  
         public boolean accept(Type type) {
 412  124
             if (type instanceof ParameterizedType) {
 413  4
                 ParameterizedType parameterizedType = (ParameterizedType) type;
 414  4
                 Type rawType = parameterizedType.getRawType();
 415  4
                 Type argumentType = parameterizedType.getActualTypeArguments()[0];
 416  4
                 return List.class.isAssignableFrom((Class<?>) rawType)
 417  3
                         && String.class.isAssignableFrom((Class<?>) argumentType);
 418  
             }
 419  120
             return false;
 420  
         }
 421  
 
 422  
         public Object convertValue(String value, Type type) {
 423  3
             if (value.trim().length() == 0)
 424  1
                 return asList();
 425  2
             return trim(asList(value.split(valueSeparator)));
 426  
         }
 427  
 
 428  
     }
 429  
 
 430  
     public static List<String> trim(List<String> values) {
 431  15
         List<String> trimmed = new ArrayList<String>();
 432  15
         for (String value : values) {
 433  51
             trimmed.add(value.trim());
 434  51
         }
 435  15
         return trimmed;
 436  
     }
 437  
 
 438  
     /**
 439  
      * Parses value to a {@link Date} using an injectable {@link DateFormat}
 440  
      * (defaults to <b>new SimpleDateFormat("dd/MM/yyyy")</b>)
 441  
      */
 442  
     public static class DateConverter implements ParameterConverter {
 443  
 
 444  1
         public static final DateFormat DEFAULT_FORMAT = new SimpleDateFormat("dd/MM/yyyy");
 445  
 
 446  
         private final DateFormat dateFormat;
 447  
 
 448  
         public DateConverter() {
 449  541
             this(DEFAULT_FORMAT);
 450  541
         }
 451  
 
 452  542
         public DateConverter(DateFormat dateFormat) {
 453  542
             this.dateFormat = dateFormat;
 454  542
         }
 455  
 
 456  
         public boolean accept(Type type) {
 457  126
             if (type instanceof Class<?>) {
 458  124
                 return Date.class.isAssignableFrom((Class<?>) type);
 459  
             }
 460  2
             return false;
 461  
         }
 462  
 
 463  
         public Object convertValue(String value, Type type) {
 464  
             try {
 465  3
                 return dateFormat.parse(value);
 466  1
             } catch (ParseException e) {
 467  1
                 throw new ParameterConvertionFailed("Failed to convert value "
 468  
                         + value
 469  
                         + " with date format "
 470  1
                         + (dateFormat instanceof SimpleDateFormat ? ((SimpleDateFormat) dateFormat).toPattern()
 471  
                                 : dateFormat), e);
 472  
             }
 473  
         }
 474  
 
 475  
     }
 476  
 
 477  
     public static class BooleanConverter implements ParameterConverter {
 478  
         private String trueValue;
 479  
         private String falseValue;
 480  
 
 481  
         public BooleanConverter() {
 482  540
             this(DEFAULT_TRUE_VALUE, DEFAULT_FALSE_VALUE);
 483  540
         }
 484  
 
 485  542
         public BooleanConverter(String trueValue, String falseValue) {
 486  542
             this.trueValue = trueValue;
 487  542
             this.falseValue = falseValue;
 488  542
         }
 489  
 
 490  
         public boolean accept(Type type) {
 491  202
             if (type instanceof Class<?>) {
 492  195
                 return Boolean.class.isAssignableFrom((Class<?>) type) || Boolean.TYPE.isAssignableFrom((Class<?>) type);
 493  
             }
 494  7
             return false;
 495  
         }
 496  
 
 497  
         public Object convertValue(String value, Type type) {
 498  
             try {
 499  17
                                 return BooleanUtils.toBoolean(value, trueValue, falseValue);
 500  2
                         } catch (IllegalArgumentException e) {
 501  2
                                 return false;
 502  
                         }
 503  
         }
 504  
     }
 505  
 
 506  
     public static class BooleanListConverter implements ParameterConverter {
 507  
         private final BooleanConverter booleanConverter;
 508  
         private String valueSeparator;
 509  
 
 510  
         public BooleanListConverter() {
 511  1
             this(DEFAULT_LIST_SEPARATOR, DEFAULT_TRUE_VALUE, DEFAULT_FALSE_VALUE);
 512  1
         }
 513  
 
 514  
         public BooleanListConverter(String valueSeparator) {
 515  0
             this(valueSeparator, DEFAULT_TRUE_VALUE, DEFAULT_FALSE_VALUE);
 516  0
         }
 517  
 
 518  1
         public BooleanListConverter(String valueSeparator, String trueValue, String falseValue) {
 519  1
             this.valueSeparator = valueSeparator;
 520  1
             booleanConverter = new BooleanConverter(trueValue, falseValue);
 521  1
         }
 522  
 
 523  
         public boolean accept(Type type) {
 524  1
             if (type instanceof ParameterizedType) {
 525  1
                 Type rawType = rawType(type);
 526  1
                 Type argumentType = argumentType(type);
 527  1
                 return List.class.isAssignableFrom((Class<?>) rawType)
 528  1
                         && Boolean.class.isAssignableFrom((Class<?>) argumentType);
 529  
             }
 530  0
             return false;
 531  
         }
 532  
 
 533  
         public Object convertValue(String value, Type type) {
 534  1
             List<String> values = trim(asList(value.split(valueSeparator)));
 535  1
             List<Boolean> booleans = new ArrayList<Boolean>();
 536  
 
 537  1
             for (String booleanValue : values) {
 538  3
                 booleans.add((Boolean) booleanConverter.convertValue(booleanValue, type));
 539  3
             }
 540  1
             return booleans;
 541  
         }
 542  
 
 543  
         private Type rawType(Type type) {
 544  1
             return ((ParameterizedType) type).getRawType();
 545  
         }
 546  
 
 547  
         private Type argumentType(Type type) {
 548  1
             return ((ParameterizedType) type).getActualTypeArguments()[0];
 549  
         }
 550  
     }
 551  
 
 552  
     /**
 553  
      * Parses value to any {@link Enum}
 554  
      */
 555  1082
     public static class EnumConverter implements ParameterConverter {
 556  
 
 557  
         public boolean accept(Type type) {
 558  125
             if (type instanceof Class<?>) {
 559  124
                 return ((Class<?>) type).isEnum();
 560  
             }
 561  1
             return false;
 562  
         }
 563  
 
 564  
         public Object convertValue(String value, Type type) {
 565  6
             String typeClass = ((Class<?>) type).getName();
 566  6
             Class<?> enumClass = (Class<?>) type;
 567  6
             Method valueOfMethod = null;
 568  
             try {
 569  6
                 valueOfMethod = enumClass.getMethod("valueOf", new Class[] { String.class });
 570  6
                 valueOfMethod.setAccessible(true);
 571  6
                 return valueOfMethod.invoke(enumClass, new Object[] { value });
 572  1
             } catch (Exception e) {
 573  1
                 throw new ParameterConvertionFailed("Failed to convert " + value + " for Enum " + typeClass, e);
 574  
             }
 575  
         }
 576  
     }
 577  
 
 578  
     /**
 579  
      * An {@link EnumConverter} allowing stories prose to be more natural.
 580  
      * Before performing the actual conversion, it transforms values to upper-case,
 581  
      * with any non-alphanumeric character replaced by an underscore ('_').
 582  
      * <p>
 583  
      * <b>Example</b>:
 584  
      * assuming we have defined the step "{@code Given I am on the $page}"
 585  
      * which is mapped to the method {@code iAmOnPage(PageEnum page)},
 586  
      * we can then write in a scenario:
 587  
      * <pre>{@code
 588  
      * Given I am on the login page
 589  
      * }</pre>
 590  
      * instead of:
 591  
      * <pre>{@code
 592  
      * Given I am on the LOGIN_PAGE
 593  
      * }</pre>
 594  
      * <p>
 595  
      * <b>Warning</b>. This <i>requires</i> enum constants to follow the
 596  
      * <a href="https://google-styleguide.googlecode.com/svn/trunk/javaguide.html#s5.2.4-constant-names">
 597  
      * standard conventions for constant names</a>, i.e. all uppercase letters,
 598  
      * with words separated by underscores.
 599  
      */
 600  1
     public static class FluentEnumConverter extends EnumConverter {
 601  
 
 602  
         @Override
 603  
         public Object convertValue(String value, Type type) {
 604  1
             return super.convertValue(value.replaceAll("\\W", "_").toUpperCase(), type);
 605  
         }
 606  
     }
 607  
 
 608  
     /**
 609  
      * Parses value to list of the same {@link Enum}, using an injectable value
 610  
      * separator (defaults to ",") and trimming each element of the list.
 611  
      */
 612  
     public static class EnumListConverter implements ParameterConverter {
 613  
         private final EnumConverter enumConverter;
 614  
         private String valueSeparator;
 615  
 
 616  
         public EnumListConverter() {
 617  540
             this(DEFAULT_LIST_SEPARATOR);
 618  540
         }
 619  
 
 620  540
         public EnumListConverter(String valueSeparator) {
 621  540
             this.enumConverter = new EnumConverter();
 622  540
             this.valueSeparator = valueSeparator;
 623  540
         }
 624  
 
 625  
         public boolean accept(Type type) {
 626  121
             if (type instanceof ParameterizedType) {
 627  1
                 Type rawType = rawType(type);
 628  1
                 Type argumentType = argumentType(type);
 629  1
                 return List.class.isAssignableFrom((Class<?>) rawType) && enumConverter.accept(argumentType);
 630  
             }
 631  120
             return false;
 632  
         }
 633  
 
 634  
         public Object convertValue(String value, Type type) {
 635  1
             Type argumentType = argumentType(type);
 636  1
             List<String> values = trim(asList(value.split(valueSeparator)));
 637  1
             List<Enum<?>> enums = new ArrayList<Enum<?>>();
 638  1
             for (String string : values) {
 639  3
                 enums.add((Enum<?>) enumConverter.convertValue(string, argumentType));
 640  3
             }
 641  1
             return enums;
 642  
         }
 643  
 
 644  
         private Type rawType(Type type) {
 645  1
             return ((ParameterizedType) type).getRawType();
 646  
         }
 647  
 
 648  
         private Type argumentType(Type type) {
 649  2
             return ((ParameterizedType) type).getActualTypeArguments()[0];
 650  
         }
 651  
     }
 652  
 
 653  
     /**
 654  
      * Converts value to {@link ExamplesTable} using a
 655  
      * {@link ExamplesTableFactory}.
 656  
      */
 657  
     public static class ExamplesTableConverter implements ParameterConverter {
 658  
 
 659  
         private final ExamplesTableFactory factory;
 660  
 
 661  
         public ExamplesTableConverter() {
 662  1
             this(new ExamplesTableFactory());
 663  1
         }
 664  
 
 665  540
         public ExamplesTableConverter(ExamplesTableFactory factory) {
 666  540
             this.factory = factory;
 667  540
         }
 668  
 
 669  
         public boolean accept(Type type) {
 670  123
             if (type instanceof Class<?>) {
 671  122
                 return ExamplesTable.class.isAssignableFrom((Class<?>) type);
 672  
             }
 673  1
             return false;
 674  
         }
 675  
 
 676  
         public Object convertValue(String value, Type type) {
 677  1
             return factory.createExamplesTable(value);
 678  
         }
 679  
 
 680  
     }
 681  
 
 682  
     /**
 683  
      * Converts ExamplesTable to list of parameters, mapped to annotated custom
 684  
      * types.
 685  
      */
 686  
     public static class ExamplesTableParametersConverter implements ParameterConverter {
 687  
 
 688  
         private final ExamplesTableFactory factory;
 689  
 
 690  
         public ExamplesTableParametersConverter() {
 691  2
             this(new ExamplesTableFactory());
 692  2
         }
 693  
 
 694  541
         public ExamplesTableParametersConverter(ExamplesTableFactory factory) {
 695  541
             this.factory = factory;
 696  541
         }
 697  
 
 698  
         public boolean accept(Type type) {
 699  126
             if (type instanceof ParameterizedType) {
 700  1
                 Class<?> rawClass = rawClass(type);
 701  1
                 Class<?> argumentClass = argumentClass(type);
 702  1
                 if (rawClass.isAnnotationPresent(AsParameters.class)
 703  1
                         || argumentClass.isAnnotationPresent(AsParameters.class)) {
 704  1
                     return true;
 705  
                 }
 706  0
             } else if (type instanceof Class) {
 707  123
                 return ((Class<?>) type).isAnnotationPresent(AsParameters.class);
 708  
             }
 709  2
             return false;
 710  
         }
 711  
 
 712  
         private Class<?> rawClass(Type type) {
 713  1
             return (Class<?>) ((ParameterizedType) type).getRawType();
 714  
         }
 715  
 
 716  
         private Class<?> argumentClass(Type type) {
 717  3
             if (type instanceof ParameterizedType) {
 718  2
                 return (Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0];
 719  
             } else {
 720  1
                 return (Class<?>) type;
 721  
             }
 722  
         }
 723  
 
 724  
         public Object convertValue(String value, Type type) {
 725  2
             List<?> rows = factory.createExamplesTable(value).getRowsAs(argumentClass(type));
 726  2
             if (type instanceof ParameterizedType) {
 727  1
                 return rows;
 728  
             }
 729  1
             return rows.iterator().next();
 730  
         }
 731  
 
 732  
     }
 733  
 
 734  
     /**
 735  
      * Invokes method on instance to return value.
 736  
      */
 737  
     public static class MethodReturningConverter implements ParameterConverter {
 738  
         private Method method;
 739  
         private Class<?> stepsType;
 740  
         private InjectableStepsFactory stepsFactory;
 741  
 
 742  4
         public MethodReturningConverter(Method method, Object instance) {
 743  4
             this.method = method;
 744  4
             this.stepsType = instance.getClass();
 745  4
             this.stepsFactory = new InstanceStepsFactory(new MostUsefulConfiguration(), instance);
 746  4
         }
 747  
 
 748  2
         public MethodReturningConverter(Method method, Class<?> stepsType, InjectableStepsFactory stepsFactory) {
 749  2
             this.method = method;
 750  2
             this.stepsType = stepsType;
 751  2
             this.stepsFactory = stepsFactory;
 752  2
         }
 753  
 
 754  
         public boolean accept(Type type) {
 755  16
             if (type instanceof Class<?>) {
 756  15
                 return method.getReturnType().isAssignableFrom((Class<?>) type);
 757  
             }
 758  1
             return false;
 759  
         }
 760  
 
 761  
         public Object convertValue(String value, Type type) {
 762  
             try {
 763  5
                 Object instance = instance();
 764  5
                 return method.invoke(instance, value);
 765  1
             } catch (Exception e) {
 766  1
                 throw new ParameterConvertionFailed("Failed to invoke method " + method + " with value " + value
 767  
                         + " in " + type, e);
 768  
             }
 769  
         }
 770  
 
 771  
         private Object instance() {
 772  5
             return stepsFactory.createInstanceOfType(stepsType);
 773  
         }
 774  
 
 775  
     }
 776  
 }