##### Coverage Report - org.jbehave.core.steps.ParameterConverters

Classes in this File | Line Coverage | Branch Coverage | Complexity
ParameterConverters |
100% | 40/40
90% | 9/10
| 2.276923076923077;2.277
ParameterConverters$BooleanConverter |
100% | 10/10
100% | 2/2
| 2.276923076923077;2.277
ParameterConverters$BooleanListConverter |
85% | 17/20
62% | 5/8
| 2.276923076923077;2.277
ParameterConverters$DateConverter |
100% | 12/12
75% | 3/4
| 2.276923076923077;2.277
ParameterConverters$EnumConverter |
100% | 11/11
100% | 2/2
| 2.276923076923077;2.277
ParameterConverters$EnumListConverter |
94% | 18/19
62% | 5/8
| 2.276923076923077;2.277
ParameterConverters$ExamplesTableConverter |
100% | 9/9
100% | 2/2
| 2.276923076923077;2.277
ParameterConverters$MethodReturningConverter |
100% | 18/18
100% | 2/2
| 2.276923076923077;2.277
ParameterConverters$NumberConverter |
95% | 63/66
93% | 45/48
| 2.276923076923077;2.277
ParameterConverters$NumberListConverter |
100% | 19/19
100% | 8/8
| 2.276923076923077;2.277
ParameterConverters$ParameterConverter |
N/A | N/A
N/A | N/A
| 2.276923076923077;2.277
ParameterConverters$ParameterConvertionFailed |
100% | 4/4
N/A | N/A
| 2.276923076923077;2.277
ParameterConverters$StringListConverter |
100% | 14/14
100% | 8/8
| 2.276923076923077;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 | 1124 |
             this(DEFAULT_STEP_MONITOR);
72 | 1124 |
         }
73 |  |
74 |  |
         /**
75 |  |
          * Creates a ParameterConverters using given StepMonitor
76 |  |
          *
77 |  |
          * @param monitor the StepMonitor to use
78 |  |
          */
79 |  |
         public ParameterConverters(StepMonitor monitor) {
80 | 1124 |
             this(monitor, DEFAULT_NUMBER_FORMAT_LOCAL, DEFAULT_LIST_SEPARATOR, DEFAULT_THREAD_SAFETY);
81 | 1124 |
         }
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 | 1125 |
             this(monitor, new ArrayList<ParameterConverter>(), threadSafe);
107 | 1125 |
             this.addConverters(defaultConverters(locale, listSeparator));
108 | 1125 |
         }
109 |  |
110 | 1156 |
         private ParameterConverters(StepMonitor monitor, List<ParameterConverter> converters, boolean threadSafe) {
111 | 1156 |
             this.monitor = monitor;
112 | 1156 |
             this.threadSafe = threadSafe;
113 | 1156 |
             this.converters = (threadSafe ? new CopyOnWriteArrayList<ParameterConverter>(converters)
114 |  |
                     : new ArrayList<ParameterConverter>(converters));
115 | 1156 |
         }
116 |  |
117 |  |
         protected ParameterConverter[] defaultConverters(Locale locale, String listSeparator) {
118 | 1125 |
             String escapedListSeparator = escapeRegexPunctuation(listSeparator);
119 | 1125 |
             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 | 1125 |
             return defaultConverters;
125 |  |
         }
126 |  |
127 |  |
         // TODO : This is a duplicate from RegExpPrefixCapturing
128 |  |
         private String escapeRegexPunctuation(String matchThis) {
129 | 1125 |
             return matchThis.replaceAll("([\\[\\]\\{\\}\\?\\^\\.\\*\\(\\)\\+\\\\])", "\\\\$1");
130 |  |
         }
131 |  |
132 |  |
         public ParameterConverters addConverters(ParameterConverter... converters) {
133 | 1128 |
             return addConverters(asList(converters));
134 |  |
         }
135 |  |
136 |  |
         public ParameterConverters addConverters(List<ParameterConverter> converters) {
137 | 1157 |
             this.converters.addAll(0, converters);
138 | 1157 |
             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 | 2262 |
             private ThreadLocal<NumberFormat> threadLocalNumberFormat = new ThreadLocal<NumberFormat>();
218 |  |
219 |  |
             public NumberConverter() {
220 | 4 |
                 this(NumberFormat.getInstance(DEFAULT_NUMBER_FORMAT_LOCAL));
221 | 4 |
             }
222 |  |
223 | 2262 |
             public NumberConverter(NumberFormat numberFormat) {
224 | 2262 |
                 synchronized (this) {
225 | 2262 |
                     this.numberFormat = numberFormat;
226 | 2262 |
                     this.threadLocalNumberFormat.set((NumberFormat) this.numberFormat.clone());
227 | 2262 |
                 }
228 | 2262 |
             }
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 | 1129 |
             public NumberListConverter(NumberFormat numberFormat, String valueSeparator) {
350 | 1129 |
                 this.numberConverter = new NumberConverter(numberFormat);
351 | 1129 |
                 this.valueSeparator = valueSeparator;
352 | 1129 |
             }
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 | 1126 |
             public StringListConverter(String valueSeparator) {
403 | 1126 |
                 this.valueSeparator = valueSeparator;
404 | 1126 |
             }
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 | 1127 |
                 this(DEFAULT_FORMAT);
445 | 1127 |
             }
446 |  |
447 | 1128 |
             public DateConverter(DateFormat dateFormat) {
448 | 1128 |
                 this.dateFormat = dateFormat;
449 | 1128 |
             }
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 | 1126 |
                 this(DEFAULT_TRUE_VALUE, DEFAULT_FALSE_VALUE);
478 | 1126 |
             }
479 |  |
480 | 1127 |
             public BooleanConverter(String trueValue, String falseValue) {
481 | 1127 |
                 this.trueValue = trueValue;
482 | 1127 |
                 this.falseValue = falseValue;
483 | 1127 |
             }
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 | 1126 |
             public ExamplesTableConverter(ExamplesTableFactory factory) {
626 | 1126 |
                 this.factory = factory;
627 | 1126 |
             }
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 |  |
     }