Coverage Report - org.jbehave.core.steps.ParameterConverters
 
Classes in this File Line Coverage Branch Coverage Complexity
ParameterConverters
100%
23/23
100%
6/6
2.514
ParameterConverters$DateConverter
100%
12/12
100%
2/2
2.514
ParameterConverters$ExamplesTableConverter
100%
10/10
100%
2/2
2.514
ParameterConverters$MethodReturningConverter
100%
10/10
100%
2/2
2.514
ParameterConverters$NumberConverter
100%
29/29
100%
34/34
2.514
ParameterConverters$NumberListConverter
100%
19/19
100%
8/8
2.514
ParameterConverters$ParameterConverter
N/A
N/A
2.514
ParameterConverters$ParameterConvertionFailed
100%
2/2
N/A
2.514
ParameterConverters$StringListConverter
100%
14/14
100%
8/8
2.514
 
 1  
 package org.jbehave.core.steps;
 2  
 
 3  
 import static java.util.Arrays.asList;
 4  
 
 5  
 import java.lang.reflect.Method;
 6  
 import java.lang.reflect.ParameterizedType;
 7  
 import java.lang.reflect.Type;
 8  
 import java.math.BigDecimal;
 9  
 import java.math.BigInteger;
 10  
 import java.text.DateFormat;
 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  
 
 18  
 import org.jbehave.core.model.ExamplesTable;
 19  
 
 20  
 /**
 21  
  * <p>
 22  
  * Facade responsible for converting parameter values to Java objects.
 23  
  * </p>
 24  
  * <p>
 25  
  * Several converters are provided out-of-the-box:
 26  
  * <ul>
 27  
  * <li>{@link ParameterConverters.NumberConverter NumberConverter}</li>
 28  
  * <li>{@link ParameterConverters.NumberListConverter NumberListConverter}</li>
 29  
  * <li>{@link ParameterConverters.StringListConverter StringListConverter}</li>
 30  
  * <li>{@link ParameterConverters.DateConverter DateConverter}</li>
 31  
  * <li>{@link ParameterConverters.ExamplesTableConverter ExamplesTableConverter}</li>
 32  
  * <li>{@link ParameterConverters.MethodReturningConverter MethodReturningConverter}</li>
 33  
  * </ul>
 34  
  * </p>
 35  
  */
 36  
 public class ParameterConverters {
 37  
 
 38  
     private static final String NEWLINES_PATTERN = "(\n)|(\r\n)";
 39  1
     private static final String SYSTEM_NEWLINE = System.getProperty("line.separator");
 40  
     private static final String COMMA = ",";
 41  1
     private static final ParameterConverter[] DEFAULT_CONVERTERS = { new NumberConverter(), new NumberListConverter(),
 42  
             new StringListConverter(), new DateConverter(), new ExamplesTableConverter() };
 43  
     private final StepMonitor monitor;
 44  230
     private final List<ParameterConverter> converters = new ArrayList<ParameterConverter>();
 45  
 
 46  
     public ParameterConverters() {
 47  230
         this(new SilentStepMonitor());
 48  230
     }
 49  
 
 50  230
     public ParameterConverters(StepMonitor monitor) {
 51  230
         this.monitor = monitor;
 52  230
         this.addConverters(DEFAULT_CONVERTERS);
 53  230
     }
 54  
 
 55  
     public ParameterConverters addConverters(ParameterConverter... converters) {
 56  230
         return addConverters(asList(converters));
 57  
     }
 58  
 
 59  
     public ParameterConverters addConverters(List<ParameterConverter> converters) {
 60  251
         this.converters.addAll(0, converters);
 61  251
         return this;
 62  
     }
 63  
 
 64  
     public Object convert(String value, Type type) {
 65  
         // check if any converters accepts type
 66  41
         for (ParameterConverter converter : converters) {
 67  171
             if (converter.accept(type)) {
 68  10
                 Object converted = converter.convertValue(value, type);
 69  10
                 monitor.convertedValueOfType(value, type, converted, converter.getClass());
 70  10
                 return converted;
 71  
             }
 72  
         }
 73  
         // default to String
 74  31
         return replaceNewlinesWithSystemNewlines(value);
 75  
     }
 76  
 
 77  
     private Object replaceNewlinesWithSystemNewlines(String value) {
 78  31
         return value.replaceAll(NEWLINES_PATTERN, SYSTEM_NEWLINE);
 79  
     }
 80  
 
 81  
     public static interface ParameterConverter {
 82  
 
 83  
         boolean accept(Type type);
 84  
 
 85  
         Object convertValue(String value, Type type);
 86  
 
 87  
     }
 88  
 
 89  
     @SuppressWarnings("serial")
 90  
     public static class ParameterConvertionFailed extends RuntimeException {
 91  
 
 92  
         public ParameterConvertionFailed(String message, Throwable cause) {
 93  4
             super(message, cause);
 94  4
         }
 95  
 
 96  
     }
 97  
 
 98  
     /**
 99  
      * <p>
 100  
      * Converts values to numbers, supporting any subclass of {@link Number}
 101  
      * (including generic Number type), and it unboxed counterpart, using a
 102  
      * {@link NumberFormat} to parse to a {@link Number} and to convert it to a
 103  
      * specific number type:
 104  
      * <ul>
 105  
      * <li>Byte, byte: {@link Number#byteValue()}</li>
 106  
      * <li>Short, short: {@link Number#shortValue()}</li>
 107  
      * <li>Integer, int: {@link Number#intValue()}</li>
 108  
      * <li>Float, float: {@link Number#floatValue()}</li>
 109  
      * <li>Long, long: {@link Number#longValue()}</li>
 110  
      * <li>Double, double: {@link Number#doubleValue()}</li>
 111  
      * <li>BigInteger: {@link BigInteger#valueOf(Long)}</li>
 112  
      * <li>BigDecimal: {@link BigDecimal#valueOf(Double)}</li></li>
 113  
      * </ul>
 114  
      * If no number format is provided, it defaults to
 115  
      * {@link NumberFormat#getInstance()}.
 116  
      * <p>
 117  
      * The localized instance {@link NumberFormat#getInstance(Locale)} can be
 118  
      * used to convert numbers in specific locales.
 119  
      * </p>
 120  
      */
 121  
     public static class NumberConverter implements ParameterConverter {
 122  
 
 123  1
         private static List<Class<?>> primitiveTypes = asList(new Class<?>[] { byte.class, short.class, int.class,
 124  
                 float.class, long.class, double.class });
 125  
 
 126  
         private final NumberFormat numberFormat;
 127  
 
 128  
         public NumberConverter() {
 129  4
             this(NumberFormat.getInstance());
 130  4
         }
 131  
 
 132  11
         public NumberConverter(NumberFormat numberFormat) {
 133  11
             this.numberFormat = numberFormat;
 134  11
         }
 135  
 
 136  
         public boolean accept(Type type) {
 137  88
             if (type instanceof Class<?>) {
 138  83
                 return Number.class.isAssignableFrom((Class<?>) type) || primitiveTypes.contains(type);
 139  
             }
 140  5
             return false;
 141  
         }
 142  
 
 143  
         public Object convertValue(String value, Type type) {
 144  
             try {
 145  102
                 Number n = numberFormat.parse(value);
 146  100
                 if (type == Byte.class || type == byte.class) {
 147  6
                     return n.byteValue();
 148  94
                 } else if (type == Short.class || type == short.class) {
 149  6
                     return n.shortValue();
 150  88
                 } else if (type == Integer.class || type == int.class) {
 151  14
                     return n.intValue();
 152  74
                 } else if (type == Float.class || type == float.class) {
 153  21
                     return n.floatValue();
 154  53
                 } else if (type == Long.class || type == long.class) {
 155  14
                     return n.longValue();
 156  39
                 } else if (type == Double.class || type == double.class) {
 157  21
                     return n.doubleValue();
 158  18
                 } else if (type == BigInteger.class) {
 159  3
                     return BigInteger.valueOf(n.longValue());
 160  15
                 } else if (type == BigDecimal.class) {
 161  3
                     return BigDecimal.valueOf(n.doubleValue());
 162  
                 } else {
 163  12
                     return n;
 164  
                 }
 165  2
             } catch (ParseException e) {
 166  2
                 throw new ParameterConvertionFailed(value, e);
 167  
             }
 168  
         }
 169  
 
 170  
     }
 171  
 
 172  
     /**
 173  
      * Converts value to list of numbers. Splits value to a list, using an
 174  
      * injectable value separator (defaulting to ",") and converts each element
 175  
      * of list via the {@link NumberConverter}, using the {@link NumberFormat}
 176  
      * provided (defaulting to {@link NumberFormat#getInstance()}).
 177  
      */
 178  
     public static class NumberListConverter implements ParameterConverter {
 179  
 
 180  
         private final NumberConverter numberConverter;
 181  
         private final String valueSeparator;
 182  
 
 183  
         public NumberListConverter() {
 184  4
             this(NumberFormat.getInstance(), COMMA);
 185  4
         }
 186  
 
 187  5
         public NumberListConverter(NumberFormat numberFormat, String valueSeparator) {
 188  5
             this.numberConverter = new NumberConverter(numberFormat);
 189  5
             this.valueSeparator = valueSeparator;
 190  5
         }
 191  
 
 192  
         public boolean accept(Type type) {
 193  38
             if (type instanceof ParameterizedType) {
 194  7
                 Type rawType = rawType(type);
 195  7
                 Type argumentType = argumentType(type);
 196  7
                 return List.class.isAssignableFrom((Class<?>) rawType)
 197  
                         && Number.class.isAssignableFrom((Class<?>) argumentType);
 198  
             }
 199  31
             return false;
 200  
         }
 201  
 
 202  
         private Type rawType(Type type) {
 203  7
             return ((ParameterizedType) type).getRawType();
 204  
         }
 205  
 
 206  
         private Type argumentType(Type type) {
 207  18
             return ((ParameterizedType) type).getActualTypeArguments()[0];
 208  
         }
 209  
 
 210  
         @SuppressWarnings("unchecked")
 211  
         public Object convertValue(String value, Type type) {
 212  11
             Class<? extends Number> argumentType = (Class<? extends Number>) argumentType(type);
 213  11
             List<String> values = trim(asList(value.split(valueSeparator)));
 214  11
             List<Number> numbers = new ArrayList<Number>();
 215  11
             for (String numberValue : values) {
 216  40
                 numbers.add((Number) numberConverter.convertValue(numberValue, argumentType));
 217  
             }
 218  10
             return numbers;
 219  
         }
 220  
 
 221  
     }
 222  
 
 223  
     /**
 224  
      * Converts value to list of String. Splits value to a list, using an
 225  
      * injectable value separator (defaults to ",") and trimming each element of
 226  
      * the list.
 227  
      */
 228  
     public static class StringListConverter implements ParameterConverter {
 229  
 
 230  
         private String valueSeparator;
 231  
 
 232  
         public StringListConverter() {
 233  2
             this(COMMA);
 234  2
         }
 235  
 
 236  2
         public StringListConverter(String valueSeparator) {
 237  2
             this.valueSeparator = valueSeparator;
 238  2
         }
 239  
 
 240  
         public boolean accept(Type type) {
 241  35
             if (type instanceof ParameterizedType) {
 242  4
                 ParameterizedType parameterizedType = (ParameterizedType) type;
 243  4
                 Type rawType = parameterizedType.getRawType();
 244  4
                 Type argumentType = parameterizedType.getActualTypeArguments()[0];
 245  4
                 return List.class.isAssignableFrom((Class<?>) rawType)
 246  
                         && String.class.isAssignableFrom((Class<?>) argumentType);
 247  
             }
 248  31
             return false;
 249  
         }
 250  
 
 251  
         public Object convertValue(String value, Type type) {
 252  3
             if (value.trim().length() == 0)
 253  1
                 return asList();
 254  2
             return trim(asList(value.split(valueSeparator)));
 255  
         }
 256  
 
 257  
     }
 258  
 
 259  
     public static List<String> trim(List<String> values) {
 260  13
         List<String> trimmed = new ArrayList<String>();
 261  13
         for (String value : values) {
 262  45
             trimmed.add(value.trim());
 263  
         }
 264  13
         return trimmed;
 265  
     }
 266  
 
 267  
     /**
 268  
      * Parses value to a {@link Date} using an injectable {@link DateFormat}
 269  
      * (defaults to <b>new SimpleDateFormat("dd/MM/yyyy")</b>)
 270  
      */
 271  
     public static class DateConverter implements ParameterConverter {
 272  
 
 273  1
         public static final DateFormat DEFAULT_FORMAT = new SimpleDateFormat("dd/MM/yyyy");
 274  
 
 275  
         private final DateFormat dateFormat;
 276  
 
 277  
         public DateConverter() {
 278  3
             this(DEFAULT_FORMAT);
 279  3
         }
 280  
 
 281  4
         public DateConverter(DateFormat dateFormat) {
 282  4
             this.dateFormat = dateFormat;
 283  4
         }
 284  
 
 285  
         public boolean accept(Type type) {
 286  37
             if (type instanceof Class<?>) {
 287  35
                 return Date.class.isAssignableFrom((Class<?>) type);
 288  
             }
 289  2
             return false;
 290  
         }
 291  
 
 292  
         public Object convertValue(String value, Type type) {
 293  
             try {
 294  3
                 return dateFormat.parse(value);
 295  1
             } catch (ParseException e) {
 296  1
                 throw new ParameterConvertionFailed("Could not convert value " + value + " with date format "
 297  
                         + dateFormat, e);
 298  
             }
 299  
         }
 300  
 
 301  
     }
 302  
 
 303  
     /**
 304  
      * Converts value to {@link ExamplesTable}
 305  
      */
 306  
     public static class ExamplesTableConverter implements ParameterConverter {
 307  
 
 308  
         private String headerSeparator;
 309  
         private String valueSeparator;
 310  
 
 311  
         public ExamplesTableConverter() {
 312  2
             this("|", "|");
 313  2
         }
 314  
 
 315  2
         public ExamplesTableConverter(String headerSeparator, String valueSeparator) {
 316  2
             this.headerSeparator = headerSeparator;
 317  2
             this.valueSeparator = valueSeparator;
 318  2
         }
 319  
 
 320  
         public boolean accept(Type type) {
 321  34
             if (type instanceof Class<?>) {
 322  33
                 return ExamplesTable.class.isAssignableFrom((Class<?>) type);
 323  
             }
 324  1
             return false;
 325  
         }
 326  
 
 327  
         public Object convertValue(String value, Type type) {
 328  1
             return new ExamplesTable(value, headerSeparator, valueSeparator);
 329  
         }
 330  
 
 331  
     }
 332  
 
 333  
     /**
 334  
      * Invokes method on instance to return value.
 335  
      */
 336  
     public static class MethodReturningConverter implements ParameterConverter {
 337  
         private Object instance;
 338  
         private Method method;
 339  
 
 340  3
         public MethodReturningConverter(Method method, Object instance) {
 341  3
             this.method = method;
 342  3
             this.instance = instance;
 343  3
         }
 344  
 
 345  
         public boolean accept(Type type) {
 346  4
             if (type instanceof Class<?>) {
 347  3
                 return method.getReturnType().isAssignableFrom((Class<?>) type);
 348  
             }
 349  1
             return false;
 350  
         }
 351  
 
 352  
         public Object convertValue(String value, Type type) {
 353  
             try {
 354  3
                 return method.invoke(instance, value);
 355  1
             } catch (Exception e) {
 356  1
                 throw new ParameterConvertionFailed("Failed to invoke method " + method + " with value " + value
 357  
                         + " in " + instance, e);
 358  
             }
 359  
         }
 360  
 
 361  
     }
 362  
 }