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