1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.juneau;
18
19 import static java.util.stream.Collectors.*;
20 import static org.apache.juneau.commons.utils.CollectionUtils.*;
21 import static org.apache.juneau.commons.utils.ThrowableUtils.*;
22 import static org.junit.jupiter.api.Assertions.*;
23
24 import java.io.*;
25 import java.lang.reflect.*;
26 import java.net.*;
27 import java.util.*;
28 import java.util.regex.*;
29 import java.util.stream.*;
30
31 import org.apache.juneau.annotation.*;
32 import org.apache.juneau.bean.swagger.*;
33 import org.apache.juneau.commons.utils.*;
34 import org.apache.juneau.junit.bct.*;
35 import org.apache.juneau.marshaller.*;
36 import org.apache.juneau.rest.*;
37 import org.apache.juneau.rest.mock.*;
38 import org.apache.juneau.serializer.*;
39 import org.apache.juneau.xml.*;
40 import org.junit.jupiter.api.*;
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142 public class TestUtils extends Utils {
143
144 private static final ThreadLocal<TimeZone> SYSTEM_TIME_ZONE = new ThreadLocal<>();
145
146 public static final ThreadLocal<Locale> SYSTEM_LOCALE = new ThreadLocal<>();
147
148
149 public static void assertEqualsAll(Object...values) {
150 for (var i = 1; i < values.length; i++) {
151 assertEquals(values[0], values[i], fs("Elements at index {0} and {1} did not match. {0}={2}, {1}={3}", 0, i, r(values[0]), r(values[i])));
152 }
153 }
154
155
156
157
158 public static void assertJson(String expected, Object value) {
159 assertEquals(expected, Json5.DEFAULT_SORTED.write(value));
160 }
161
162
163
164
165
166 public static String pipedLines(Object value) {
167 return r(value).replaceAll("\\r?\\n", "|");
168 }
169
170
171 public static void assertNotEqualsAny(Object actual, Object...values) {
172 assertNotNull(actual, "Value was null.");
173 for (var i = 0; i < values.length; i++) {
174 assertNotEquals(values[i], actual, fs("Element at index {0} unexpectedly matched. expected={1}, actual={2}", i, values[i], s(actual)));
175 }
176 }
177
178
179
180
181 public static void assertSerialized(Object actual, WriterSerializer s, String expected) {
182 assertEquals(expected, s.toString(actual));
183 }
184
185 public static <T extends Throwable> T assertThrowable(Class<? extends Throwable> expectedType, String expectedSubstring, T t) {
186 var messages = getMessages(t);
187 assertTrue(messages.contains(expectedSubstring), fs("Expected message to contain: {0}.\nActual:\n{1}", expectedSubstring, messages));
188 return t;
189 }
190
191 public static <T extends Throwable> T assertThrowsWithMessage(Class<T> expectedType, List<String> expectedSubstrings, org.junit.jupiter.api.function.Executable executable) {
192 var exception = Assertions.assertThrows(expectedType, executable);
193 var messages = getMessages(exception);
194 expectedSubstrings.stream().forEach(x -> assertTrue(messages.contains(x), fs("Expected message to contain: {0}.\nActual:\n{1}", x, messages)));
195 return exception;
196 }
197
198 public static <T extends Throwable> T assertThrowsWithMessage(Class<T> expectedType, String expectedSubstring, org.junit.jupiter.api.function.Executable executable) {
199 var exception = Assertions.assertThrows(expectedType, executable);
200 var messages = getMessages(exception);
201 assertTrue(messages.contains(expectedSubstring), fs("Expected message to contain: {0}.\nActual:\n{1}", expectedSubstring, messages));
202 return exception;
203 }
204
205
206
207
208 public static final void checkXmlWhitespace(String out) throws SerializeException {
209 if (out.indexOf('\u0000') != -1) {
210 for (var s : out.split("\u0000"))
211 checkXmlWhitespace(s);
212 return;
213 }
214
215 var indent = -1;
216 var startTag = Pattern.compile("^(\\s*)<[^/>]+(\\s+\\S+=['\"]\\S*['\"])*\\s*>$");
217 var endTag = Pattern.compile("^(\\s*)</[^>]+>$");
218 var combinedTag = Pattern.compile("^(\\s*)<[^>/]+(\\s+\\S+=['\"]\\S*['\"])*\\s*/>$");
219 var contentOnly = Pattern.compile("^(\\s*)[^\\s\\<]+$");
220 var tagWithContent = Pattern.compile("^(\\s*)<[^>]+>.*</[^>]+>$");
221 var lines = out.split("\n");
222 try {
223 for (var i = 0; i < lines.length; i++) {
224 var line = lines[i];
225 var m = startTag.matcher(line);
226 if (m.matches()) {
227 indent++;
228 if (m.group(1).length() != indent)
229 throw new SerializeException("Wrong indentation detected on start tag line ''{0}''", i+1);
230 continue;
231 }
232 m = endTag.matcher(line);
233 if (m.matches()) {
234 if (m.group(1).length() != indent)
235 throw new SerializeException("Wrong indentation detected on end tag line ''{0}''", i+1);
236 indent--;
237 continue;
238 }
239 m = combinedTag.matcher(line);
240 if (m.matches()) {
241 indent++;
242 if (m.group(1).length() != indent)
243 throw new SerializeException("Wrong indentation detected on combined tag line ''{0}''", i+1);
244 indent--;
245 continue;
246 }
247 m = contentOnly.matcher(line);
248 if (m.matches()) {
249 indent++;
250 if (m.group(1).length() != indent)
251 throw new SerializeException("Wrong indentation detected on content-only line ''{0}''", i+1);
252 indent--;
253 continue;
254 }
255 m = tagWithContent.matcher(line);
256 if (m.matches()) {
257 indent++;
258 if (m.group(1).length() != indent)
259 throw new SerializeException("Wrong indentation detected on tag-with-content line ''{0}''", i+1);
260 indent--;
261 continue;
262 }
263 throw new SerializeException("Unmatched whitespace line at line number ''{0}''", i+1);
264 }
265 if (indent != -1)
266 throw new SerializeException("Possible unmatched tag. indent=''{0}''", indent);
267 } catch (SerializeException e) {
268 printLines(lines);
269 throw e;
270 }
271 }
272
273
274
275
276
277
278 public static Object getBeanProp(Object o, String name) {
279 return safe(() -> {
280 var f = (Field)null;
281 var c = o.getClass();
282 var n = Character.toUpperCase(name.charAt(0)) + name.substring(1);
283 var m = Arrays.stream(c.getMethods()).filter(x -> x.getName().equals("is"+n) && x.getParameterCount() == 0 && x.getAnnotation(BeanIgnore.class) == null).findFirst().orElse(null);
284 if (m != null) {
285 m.setAccessible(true);
286 return m.invoke(o);
287 }
288 m = Arrays.stream(c.getMethods()).filter(x -> x.getName().equals("get"+n) && x.getParameterCount() == 0 && x.getAnnotation(BeanIgnore.class) == null).findFirst().orElse(null);
289 if (m != null) {
290 m.setAccessible(true);
291 return m.invoke(o);
292 }
293 m = Arrays.stream(c.getMethods()).filter(x -> x.getName().equals("get") && x.getParameterCount() == 1 && x.getParameterTypes()[0] == String.class && x.getAnnotation(BeanIgnore.class) == null).findFirst().orElse(null);
294 if (m != null) {
295 m.setAccessible(true);
296 return m.invoke(o, name);
297 }
298 var c2 = c;
299 while (f == null && c2 != null) {
300 f = Arrays.stream(c2.getDeclaredFields()).filter(x -> x.getName().equals(name)).findFirst().orElse(null);
301 c2 = c2.getSuperclass();
302 }
303 if (f != null) {
304 f.setAccessible(true);
305 return f.get(o);
306 }
307 m = Arrays.stream(c.getMethods()).filter(x -> x.getName().equals(name) && x.getParameterCount() == 0).findFirst().orElse(null);
308 if (m != null) {
309 m.setAccessible(true);
310 return m.invoke(o);
311 }
312 throw rex("Property {0} not found on object of type {1}", name, cn(o));
313 });
314 }
315
316 private static String getMessages(Throwable t) {
317 return Stream.iterate(t, Throwable::getCause).takeWhile(e -> e != null).map(Throwable::getMessage).collect(joining("\n"));
318 }
319
320
321
322
323 public static Swagger getSwagger(Class<?> c) {
324 try {
325 var r = c.getDeclaredConstructor().newInstance();
326 var rc = RestContext.create(r.getClass(),null,null).init(()->r).build();
327 var ctx = RestOpContext.create(TestUtils.class.getMethod("getSwagger", Class.class), rc).build();
328 var session = RestSession.create(rc).resource(r).req(new MockServletRequest()).res(new MockServletResponse()).build();
329 var req = ctx.createRequest(session);
330 var ip = rc.getSwaggerProvider();
331 return ip.getSwagger(rc, req.getLocale());
332 } catch (Exception e) {
333 throw new RuntimeException(e);
334 }
335 }
336
337
338
339
340
341
342
343 public static final ByteArrayInputStream inputStream(String in) {
344 return new ByteArrayInputStream(in.getBytes());
345 }
346
347 public static String json(Object o) {
348 return Json5.DEFAULT_SORTED.write(o);
349 }
350
351 public static <T> T json(String o, Class<T> c) {
352 return safe(()->Json5.DEFAULT_SORTED.read(o, c));
353 }
354
355 public static <T> T jsonRoundTrip(T o, Class<T> c) {
356 return json(json(o), c);
357 }
358
359
360
361
362
363
364
365 public static final StringReader reader(String in) {
366 return new StringReader(in);
367 }
368
369
370
371
372
373
374
375 public static final void setLocale(Locale v) {
376 SYSTEM_LOCALE.set(Locale.getDefault());
377 Locale.setDefault(v);
378 }
379
380
381
382
383
384
385
386 public static final synchronized void setTimeZone(String v) {
387 SYSTEM_TIME_ZONE.set(TimeZone.getDefault());
388 TimeZone.setDefault(TimeZone.getTimeZone(v));
389 }
390
391 public static final void unsetLocale() {
392 Locale.setDefault(SYSTEM_LOCALE.get());
393 }
394
395 public static final synchronized void unsetTimeZone() {
396 TimeZone.setDefault(SYSTEM_TIME_ZONE.get());
397 }
398
399
400
401
402 public static URL url(String value) {
403 return safe(()->new URI(value).toURL());
404 }
405
406
407
408
409 public static final void validateXml(Object o) throws Exception {
410 validateXml(o, XmlSerializer.DEFAULT_NS_SQ);
411 }
412
413
414
415
416 public static final void validateXml(Object o, XmlSerializer s) throws Exception {
417 s = s.copy().ws().ns().addNamespaceUrisToRoot().build();
418 var xml = s.serialize(o);
419 checkXmlWhitespace(xml);
420 }
421
422 public static final <T> BeanTester<T> testBean(T bean) {
423 return (BeanTester<T>) new BeanTester<>().bean(bean);
424 }
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449 public static List<String> extractXml(String html, String elementName, Map<String,String> withAttributes) {
450 List<String> results = list();
451
452 if (html == null || elementName == null) {
453 return results;
454 }
455
456
457 var openTag = "<" + elementName;
458 int searchPos = 0;
459
460 while ((searchPos = html.indexOf(openTag, searchPos)) != -1) {
461
462 int tagEnd = html.indexOf('>', searchPos);
463 if (tagEnd == -1) break;
464
465 var fullOpenTag = html.substring(searchPos, tagEnd + 1);
466
467
468 var matches = true;
469 if (withAttributes != null && !withAttributes.isEmpty()) {
470 for (var entry : withAttributes.entrySet()) {
471 var attrName = entry.getKey();
472 var attrValue = entry.getValue();
473
474
475 var pattern1 = attrName + "=\"" + attrValue + "\"";
476 var pattern2 = attrName + "='" + attrValue + "'";
477
478 if (!fullOpenTag.contains(pattern1) && !fullOpenTag.contains(pattern2)) {
479 matches = false;
480 break;
481 }
482 }
483 }
484
485 if (matches) {
486
487 int contentStart = tagEnd + 1;
488 int depth = 1;
489 int pos = contentStart;
490
491 while (pos < html.length() && depth > 0) {
492
493 int nextOpen = html.indexOf("<" + elementName, pos);
494 int nextClose = html.indexOf("</" + elementName + ">", pos);
495
496
497 if (nextOpen != -1 && (nextOpen < nextClose || nextClose == -1)) {
498 if (nextOpen + elementName.length() + 1 < html.length()) {
499 var nextChar = html.charAt(nextOpen + elementName.length() + 1);
500 if (nextChar == ' ' || nextChar == '>' || nextChar == '/') {
501 depth++;
502 pos = nextOpen + elementName.length() + 1;
503 continue;
504 }
505 }
506
507 pos = nextOpen + 1;
508 continue;
509 }
510
511 if (nextClose != -1) {
512 depth--;
513 if (depth == 0) {
514
515 results.add(html.substring(contentStart, nextClose));
516 break;
517 }
518 pos = nextClose + elementName.length() + 3;
519 } else {
520
521 break;
522 }
523 }
524 }
525
526 searchPos = tagEnd + 1;
527 }
528
529 return results;
530 }
531 }