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 | |
|
22 | |
|
23 | |
|
24 | |
|
25 | |
|
26 | |
|
27 | |
|
28 | |
|
29 | |
|
30 | |
|
31 | |
|
32 | |
|
33 | |
|
34 | |
|
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 | |
|
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 | |
|
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 | |
|
100 | |
|
101 | |
|
102 | |
|
103 | |
|
104 | |
|
105 | |
|
106 | |
|
107 | |
|
108 | |
|
109 | |
|
110 | |
|
111 | |
|
112 | |
|
113 | |
|
114 | |
|
115 | |
|
116 | |
|
117 | |
|
118 | |
|
119 | |
|
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; |
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; |
178 | 9 | i = value.length(); |
179 | |
} |
180 | |
} |
181 | |
} |
182 | 9 | return value; |
183 | |
} |
184 | |
} |
185 | |
|
186 | |
|
187 | |
|
188 | |
|
189 | |
|
190 | |
|
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 | |
|
239 | |
|
240 | |
|
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 | |
|
283 | |
|
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 | |
|
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 | |
|
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 | |
} |