Coverage Report - org.jbehave.core.steps.ParameterConverters
 
Classes in this File Line Coverage Branch Coverage Complexity
ParameterConverters
100%
23/23
100%
6/6
2.583
ParameterConverters$DateConverter
100%
12/12
75%
3/4
2.583
ParameterConverters$ExamplesTableConverter
100%
10/10
100%
2/2
2.583
ParameterConverters$MethodReturningConverter
100%
10/10
100%
2/2
2.583
ParameterConverters$NumberConverter
100%
38/38
100%
40/40
2.583
ParameterConverters$NumberListConverter
100%
19/19
100%
8/8
2.583
ParameterConverters$ParameterConverter
N/A
N/A
2.583
ParameterConverters$ParameterConvertionFailed
100%
2/2
N/A
2.583
ParameterConverters$StringListConverter
100%
14/14
100%
8/8
2.583
 
 1  
 package org.jbehave.core.steps;
 2  
 
 3  
 import org.jbehave.core.model.ExamplesTable;
 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 static java.util.Arrays.asList;
 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  241
     private final List<ParameterConverter> converters = new ArrayList<ParameterConverter>();
 45  
 
 46  
     public ParameterConverters() {
 47  241
         this(new SilentStepMonitor());
 48  241
     }
 49  
 
 50  241
     public ParameterConverters(StepMonitor monitor) {
 51  241
         this.monitor = monitor;
 52  241
         this.addConverters(DEFAULT_CONVERTERS);
 53  241
     }
 54  
 
 55  
     public ParameterConverters addConverters(ParameterConverter... converters) {
 56  241
         return addConverters(asList(converters));
 57  
     }
 58  
 
 59  
     public ParameterConverters addConverters(List<ParameterConverter> converters) {
 60  262
         this.converters.addAll(0, converters);
 61  262
         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  108
                 Number n = numberFormat.parse(value);
 146  106
                 if (type == Byte.class || type == byte.class) {
 147  6
                     return n.byteValue();
 148  100
                 } else if (type == Short.class || type == short.class) {
 149  6
                     return n.shortValue();
 150  94
                 } else if (type == Integer.class || type == int.class) {
 151  14
                     return n.intValue();
 152  80
                 } else if (type == Float.class || type == float.class) {
 153  21
                     return n.floatValue();
 154  59
                 } else if (type == Long.class || type == long.class) {
 155  14
                     return n.longValue();
 156  45
                 } else if (type == Double.class || type == double.class) {
 157  21
                     return n.doubleValue();
 158  24
                 } else if (type == BigInteger.class) {
 159  3
                     return BigInteger.valueOf(n.longValue());
 160  21
                 } else if (type == BigDecimal.class) {
 161  9
                     return new BigDecimal(ensureAllDigitsArePresent(n.doubleValue(), value));
 162  
                 } else {
 163  12
                     return n;
 164  
                 }
 165  2
             } catch (ParseException e) {
 166  2
                 throw new ParameterConvertionFailed(value, e);
 167  
             }
 168  
         }
 169  
 
 170  
         private static String ensureAllDigitsArePresent(double valueReducedToDouble, String valueAsString) {
 171  9
             String value = "" + valueReducedToDouble; // might have dropped trailing "0";
 172  9
             int i = -1;
 173  45
             for (char c : valueAsString.toCharArray()) {
 174  36
                 if (Character.isDigit(c)) {
 175  27
                     i = value.indexOf(c, i+1);
 176  27
                     if (i == -1) {
 177  9
                         value = value + c; // should be a "0" if missing
 178  9
                         i = value.length();
 179  
                     }
 180  
                 }
 181  
             }
 182  9
             return value;
 183  
         }
 184  
     }
 185  
 
 186  
     /**
 187  
      * Converts value to list of numbers. Splits value to a list, using an
 188  
      * injectable value separator (defaulting to ",") and converts each element
 189  
      * of list via the {@link NumberConverter}, using the {@link NumberFormat}
 190  
      * provided (defaulting to {@link NumberFormat#getInstance()}).
 191  
      */
 192  
     public static class NumberListConverter implements ParameterConverter {
 193  
 
 194  
         private final NumberConverter numberConverter;
 195  
         private final String valueSeparator;
 196  
 
 197  
         public NumberListConverter() {
 198  3
             this(NumberFormat.getInstance(), COMMA);
 199  3
         }
 200  
 
 201  5
         public NumberListConverter(NumberFormat numberFormat, String valueSeparator) {
 202  5
             this.numberConverter = new NumberConverter(numberFormat);
 203  5
             this.valueSeparator = valueSeparator;
 204  5
         }
 205  
 
 206  
         public boolean accept(Type type) {
 207  38
             if (type instanceof ParameterizedType) {
 208  7
                 Type rawType = rawType(type);
 209  7
                 Type argumentType = argumentType(type);
 210  7
                 return List.class.isAssignableFrom((Class<?>) rawType)
 211  
                         && Number.class.isAssignableFrom((Class<?>) argumentType);
 212  
             }
 213  31
             return false;
 214  
         }
 215  
 
 216  
         private Type rawType(Type type) {
 217  7
             return ((ParameterizedType) type).getRawType();
 218  
         }
 219  
 
 220  
         private Type argumentType(Type type) {
 221  18
             return ((ParameterizedType) type).getActualTypeArguments()[0];
 222  
         }
 223  
 
 224  
         @SuppressWarnings("unchecked")
 225  
         public Object convertValue(String value, Type type) {
 226  11
             Class<? extends Number> argumentType = (Class<? extends Number>) argumentType(type);
 227  11
             List<String> values = trim(asList(value.split(valueSeparator)));
 228  11
             List<Number> numbers = new ArrayList<Number>();
 229  11
             for (String numberValue : values) {
 230  40
                 numbers.add((Number) numberConverter.convertValue(numberValue, argumentType));
 231  
             }
 232  10
             return numbers;
 233  
         }
 234  
 
 235  
     }
 236  
 
 237  
     /**
 238  
      * Converts value to list of String. Splits value to a list, using an
 239  
      * injectable value separator (defaults to ",") and trimming each element of
 240  
      * the list.
 241  
      */
 242  
     public static class StringListConverter implements ParameterConverter {
 243  
 
 244  
         private String valueSeparator;
 245  
 
 246  
         public StringListConverter() {
 247  2
             this(COMMA);
 248  2
         }
 249  
 
 250  2
         public StringListConverter(String valueSeparator) {
 251  2
             this.valueSeparator = valueSeparator;
 252  2
         }
 253  
 
 254  
         public boolean accept(Type type) {
 255  35
             if (type instanceof ParameterizedType) {
 256  4
                 ParameterizedType parameterizedType = (ParameterizedType) type;
 257  4
                 Type rawType = parameterizedType.getRawType();
 258  4
                 Type argumentType = parameterizedType.getActualTypeArguments()[0];
 259  4
                 return List.class.isAssignableFrom((Class<?>) rawType)
 260  
                         && String.class.isAssignableFrom((Class<?>) argumentType);
 261  
             }
 262  31
             return false;
 263  
         }
 264  
 
 265  
         public Object convertValue(String value, Type type) {
 266  3
             if (value.trim().length() == 0)
 267  1
                 return asList();
 268  2
             return trim(asList(value.split(valueSeparator)));
 269  
         }
 270  
 
 271  
     }
 272  
 
 273  
     public static List<String> trim(List<String> values) {
 274  13
         List<String> trimmed = new ArrayList<String>();
 275  13
         for (String value : values) {
 276  45
             trimmed.add(value.trim());
 277  
         }
 278  13
         return trimmed;
 279  
     }
 280  
 
 281  
     /**
 282  
      * Parses value to a {@link Date} using an injectable {@link DateFormat}
 283  
      * (defaults to <b>new SimpleDateFormat("dd/MM/yyyy")</b>)
 284  
      */
 285  
     public static class DateConverter implements ParameterConverter {
 286  
 
 287  1
         public static final DateFormat DEFAULT_FORMAT = new SimpleDateFormat("dd/MM/yyyy");
 288  
 
 289  
         private final DateFormat dateFormat;
 290  
 
 291  
         public DateConverter() {
 292  3
             this(DEFAULT_FORMAT);
 293  3
         }
 294  
 
 295  4
         public DateConverter(DateFormat dateFormat) {
 296  4
             this.dateFormat = dateFormat;
 297  4
         }
 298  
 
 299  
         public boolean accept(Type type) {
 300  37
             if (type instanceof Class<?>) {
 301  35
                 return Date.class.isAssignableFrom((Class<?>) type);
 302  
             }
 303  2
             return false;
 304  
         }
 305  
 
 306  
         public Object convertValue(String value, Type type) {
 307  
             try {
 308  3
                 return dateFormat.parse(value);
 309  1
             } catch (ParseException e) {
 310  1
                 throw new ParameterConvertionFailed("Could not convert value " + value + " with date format "
 311  
                         + (dateFormat instanceof SimpleDateFormat ? ((SimpleDateFormat) dateFormat).toPattern() : dateFormat), e);
 312  
             }
 313  
         }
 314  
 
 315  
     }
 316  
 
 317  
     /**
 318  
      * Converts value to {@link ExamplesTable}
 319  
      */
 320  
     public static class ExamplesTableConverter implements ParameterConverter {
 321  
 
 322  
         private String headerSeparator;
 323  
         private String valueSeparator;
 324  
 
 325  
         public ExamplesTableConverter() {
 326  2
             this("|", "|");
 327  2
         }
 328  
 
 329  2
         public ExamplesTableConverter(String headerSeparator, String valueSeparator) {
 330  2
             this.headerSeparator = headerSeparator;
 331  2
             this.valueSeparator = valueSeparator;
 332  2
         }
 333  
 
 334  
         public boolean accept(Type type) {
 335  34
             if (type instanceof Class<?>) {
 336  33
                 return ExamplesTable.class.isAssignableFrom((Class<?>) type);
 337  
             }
 338  1
             return false;
 339  
         }
 340  
 
 341  
         public Object convertValue(String value, Type type) {
 342  1
             return new ExamplesTable(value, headerSeparator, valueSeparator);
 343  
         }
 344  
 
 345  
     }
 346  
 
 347  
     /**
 348  
      * Invokes method on instance to return value.
 349  
      */
 350  
     public static class MethodReturningConverter implements ParameterConverter {
 351  
         private Object instance;
 352  
         private Method method;
 353  
 
 354  3
         public MethodReturningConverter(Method method, Object instance) {
 355  3
             this.method = method;
 356  3
             this.instance = instance;
 357  3
         }
 358  
 
 359  
         public boolean accept(Type type) {
 360  4
             if (type instanceof Class<?>) {
 361  3
                 return method.getReturnType().isAssignableFrom((Class<?>) type);
 362  
             }
 363  1
             return false;
 364  
         }
 365  
 
 366  
         public Object convertValue(String value, Type type) {
 367  
             try {
 368  3
                 return method.invoke(instance, value);
 369  1
             } catch (Exception e) {
 370  1
                 throw new ParameterConvertionFailed("Failed to invoke method " + method + " with value " + value
 371  
                         + " in " + instance, e);
 372  
             }
 373  
         }
 374  
 
 375  
     }
 376  
 }