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 | |
|
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 | 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 | |
|
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 | 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 | |
|
174 | |
|
175 | |
|
176 | |
|
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 | |
|
225 | |
|
226 | |
|
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 | |
|
269 | |
|
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 | |
|
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 | |
|
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 | |
} |