View Javadoc
1   package net.avcompris.commons3.core.tests;
2   
3   import static com.google.common.base.Preconditions.checkNotNull;
4   import static com.google.common.collect.Sets.newHashSet;
5   import static java.util.Locale.ENGLISH;
6   import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric;
7   import static org.apache.commons.lang3.StringUtils.normalizeSpace;
8   import static org.apache.commons.lang3.StringUtils.substringBefore;
9   import static org.apache.commons.lang3.StringUtils.uncapitalize;
10  import static org.junit.jupiter.api.Assertions.fail;
11  
12  import java.lang.reflect.InvocationTargetException;
13  import java.lang.reflect.Method;
14  import java.util.Comparator;
15  import java.util.Set;
16  import java.util.concurrent.Callable;
17  
18  import javax.annotation.Nullable;
19  
20  import org.apache.commons.lang3.NotImplementedException;
21  
22  import net.avcompris.commons3.api.Entities;
23  import net.avcompris.commons3.api.EntitiesQuery.EnumSortBy;
24  import net.avcompris.commons3.api.Entity;
25  import net.avcompris.commons3.api.User;
26  import net.avcompris.commons3.api.exception.ServiceException;
27  import net.avcompris.commons3.api.exception.UnauthorizedException;
28  import net.avcompris.commons3.core.Permissions;
29  import net.avcompris.commons3.databeans.DataBeans;
30  import net.avcompris.commons3.types.DateTimeHolder;
31  import net.avcompris.commons3.utils.Clock;
32  import net.avcompris.commons3.utils.ClockImpl;
33  import net.avcompris.commons3.utils.LogFactory;
34  
35  public abstract class CoreTestUtils {
36  
37  	private static final Set<String> randoms = newHashSet();
38  
39  	public static String newCorrelationId() {
40  
41  		final String correlationId = "C-" //
42  				+ System.currentTimeMillis() + "-" //
43  				+ randomAlphanumeric(20);
44  
45  		LogFactory.setCorrelationId(correlationId);
46  
47  		return correlationId;
48  	}
49  
50  	public static Permissions grantAll() {
51  
52  		return new Permissions() {
53  
54  			@Override
55  			public void assertAuthorized(final String correlationId, final User user,
56  					final Object... contextNameValuePairs) throws UnauthorizedException {
57  
58  				return; // do nothing
59  			}
60  		};
61  	}
62  
63  	public static Clock defaultClock() {
64  
65  		return new ClockImpl();
66  	}
67  
68  	private static String randomNoDuplicate(final Callable<String> c) {
69  
70  		for (final long startMs = System.currentTimeMillis(); System.currentTimeMillis() < startMs + 1000;) {
71  
72  			final String s;
73  
74  			try {
75  
76  				s = c.call();
77  
78  			} catch (final RuntimeException e) {
79  
80  				throw e;
81  
82  			} catch (final Exception e) {
83  
84  				throw new RuntimeException(e);
85  			}
86  
87  			synchronized (randoms) {
88  
89  				if (!randoms.contains(s)) {
90  
91  					randoms.add(s);
92  
93  					return s;
94  				}
95  			}
96  		}
97  
98  		throw new IllegalStateException("Timeout after 1000 ms.");
99  	}
100 
101 	public static String random8() {
102 
103 		return randomNoDuplicate(() -> randomAlphanumeric(8));
104 	}
105 
106 	public static String random20() {
107 
108 		return randomNoDuplicate(() -> randomAlphanumeric(20));
109 	}
110 
111 	public static String random20(@Nullable final String prefix) {
112 
113 		return randomNoDuplicate(() -> random(20, prefix));
114 	}
115 
116 	public static String random32(@Nullable final String prefix) {
117 
118 		return randomNoDuplicate(() -> random(32, prefix));
119 	}
120 
121 	public static String random40(@Nullable final String prefix) {
122 
123 		return randomNoDuplicate(() -> random(40, prefix));
124 	}
125 
126 	public static String randomLowercase40(@Nullable final String prefix) {
127 
128 		return randomNoDuplicate(() -> randomLowercase(40, prefix));
129 	}
130 
131 	public static String randomEmail20() {
132 
133 		return randomNoDuplicate(()
134 
135 		-> randomAlphanumeric(8) + "@" + randomAlphanumeric(7) + ".avcompris.com");
136 	}
137 
138 	private static String random(final int length, @Nullable final String prefix) {
139 
140 		return (prefix //
141 				+ System.currentTimeMillis() + "-" //
142 				+ randomAlphanumeric(length) //
143 		).substring(0, length);
144 	}
145 
146 	private static String randomLowercase(final int length, @Nullable final String prefix) {
147 
148 		return random(length, prefix).toLowerCase(ENGLISH);
149 	}
150 
151 	public static <T extends Entity> void assertAreSorted(final EnumSortBy sortBy, final Entities<T> entities) {
152 
153 		checkNotNull(entities, "entities");
154 		checkNotNull(sortBy, "sortBy");
155 
156 		DataBeans.validate(entities);
157 
158 		final StringBuilder sb = new StringBuilder();
159 
160 		final String name = sortBy.name();
161 
162 		final boolean isDesc = name.endsWith("_DESC");
163 
164 		for (int i = 8; i < (isDesc ? name.length() - 5 : name.length()); ++i) {
165 
166 			final char c = name.charAt(i);
167 
168 			if (c == '_') {
169 
170 				++i;
171 
172 				sb.append(Character.toUpperCase(name.charAt(i)));
173 
174 			} else {
175 
176 				sb.append(Character.toLowerCase(c));
177 			}
178 		}
179 
180 		if (isDesc) {
181 			sb.append(" DESC");
182 		}
183 
184 		assertAreSorted(sb.toString(), entities.getResults());
185 	}
186 
187 	public static <T extends Entity> void assertAreSorted(final String sortBy, final Entities<T> entities) {
188 
189 		checkNotNull(entities, "entities");
190 		checkNotNull(sortBy, "sortBy");
191 
192 		DataBeans.validate(entities);
193 
194 		assertAreSorted(sortBy, entities.getResults());
195 	}
196 
197 	@SafeVarargs
198 	public static <T extends Entity> void assertAreSorted(final String sortBy, final T... results) {
199 
200 		checkNotNull(results, "results");
201 		checkNotNull(sortBy, "sortBy");
202 
203 		if (results.length == 0 || results.length == 1) {
204 			return;
205 		}
206 
207 		final boolean isDesc;
208 		final String propertyName;
209 
210 		final String trim = normalizeSpace(sortBy);
211 
212 		if (trim.endsWith(" DESC")) {
213 
214 			isDesc = true;
215 			propertyName = substringBefore(trim, " ");
216 
217 		} else if (trim.endsWith(" ASC")) {
218 
219 			isDesc = false;
220 			propertyName = substringBefore(trim, " ");
221 
222 		} else {
223 
224 			isDesc = false;
225 			propertyName = trim;
226 		}
227 
228 		final FieldExtractor extractor = extractFieldExtractor(results.getClass().getComponentType(), propertyName);
229 
230 		final Comparator<Object> comparator = getComparator(extractor.getReturnType(), isDesc);
231 
232 		Object current = extractor.invoke(results[0]);
233 
234 		for (int i = 1; i < results.length; ++i) {
235 
236 			final Object next = extractor.invoke(results[i]);
237 
238 			if (!isDesc) {
239 
240 				if (next == null) {
241 
242 					current = null;
243 
244 					continue;
245 
246 				} else if (current == null) {
247 
248 					fail("Values should be sorted: [" + (i - 1) + "]." + propertyName + ": " + current //
249 							+ ", [" + i + "]." + propertyName + ": " + next);
250 
251 				}
252 			}
253 
254 			if (isDesc) {
255 
256 				if (current == null) {
257 
258 					current = next;
259 
260 					continue;
261 
262 				} else if (next == null) {
263 
264 					fail("Values should be sorted: [" + (i - 1) + "]." + propertyName + ": " + current //
265 							+ ", [" + i + "]." + propertyName + ": " + next);
266 
267 				}
268 			}
269 
270 			if (comparator.compare(next, current) < 0) {
271 
272 				fail("Values should be sorted: [" + (i - 1) + "]." + propertyName + ": " + current //
273 						+ ", [" + i + "]." + propertyName + ": " + next);
274 			}
275 
276 			current = next;
277 		}
278 	}
279 
280 	private static Comparator<Object> getComparator(final Class<?> type, final boolean isDesc) {
281 
282 		checkNotNull(type, "type");
283 
284 		final int sign = isDesc ? -1 : 1;
285 
286 		if (String.class.equals(type)) {
287 
288 			return new Comparator<Object>() {
289 
290 				@Override
291 				public int compare(final Object o1, final Object o2) {
292 
293 					return sign * ((String) o1).compareToIgnoreCase((String) o2);
294 
295 					// return sign * ((String) o1).compareTo((String) o2);
296 				}
297 			};
298 
299 		} else if (int.class.equals(type)) {
300 
301 			return new Comparator<Object>() {
302 
303 				@Override
304 				public int compare(final Object o1, final Object o2) {
305 
306 					return sign * ((Integer) o1).compareTo((Integer) o2);
307 				}
308 			};
309 
310 		} else if (boolean.class.equals(type)) {
311 
312 			return new Comparator<Object>() {
313 
314 				@Override
315 				public int compare(final Object o1, final Object o2) {
316 
317 					return sign * ((Boolean) o1).compareTo((Boolean) o2);
318 				}
319 			};
320 
321 		} else if (DateTimeHolder.class.equals(type)) {
322 
323 			return new Comparator<Object>() {
324 
325 				@Override
326 				public int compare(final Object o1, final Object o2) {
327 
328 					return sign * ((DateTimeHolder) o1).compareTo((DateTimeHolder) o2);
329 				}
330 			};
331 
332 		} else if (type.isEnum()) {
333 
334 			return new Comparator<Object>() {
335 
336 				@Override
337 				public int compare(final Object o1, final Object o2) {
338 
339 					return sign * o1.toString().compareTo(o2.toString());
340 				}
341 			};
342 
343 		} else {
344 
345 			throw new NotImplementedException("type: " + type.getName());
346 		}
347 	}
348 
349 	private interface FieldExtractor {
350 
351 		Class<?> getReturnType();
352 
353 		@Nullable
354 		Object invoke(Object object);
355 	}
356 
357 	private static final class FieldExtractorFromGetter implements FieldExtractor {
358 
359 		private final Method getter;
360 
361 		@Nullable
362 		private final FieldExtractor next;
363 
364 		public FieldExtractorFromGetter(final Method getter) {
365 
366 			this.getter = checkNotNull(getter, "getter");
367 			this.next = null;
368 		}
369 
370 		public FieldExtractorFromGetter(final Method getter, final FieldExtractor next) {
371 
372 			this.getter = checkNotNull(getter, "getter");
373 			this.next = checkNotNull(next, "next");
374 		}
375 
376 		@Override
377 		public Class<?> getReturnType() {
378 
379 			return next == null
380 
381 					? getter.getReturnType()
382 
383 					: next.getReturnType();
384 		}
385 
386 		@Override
387 		public Object invoke(final Object object) {
388 
389 			checkNotNull(object, "object");
390 
391 			final Object value;
392 
393 			try {
394 
395 				value = getter.invoke(object);
396 
397 			} catch (final IllegalAccessException e) {
398 
399 				throw new RuntimeException(e);
400 
401 			} catch (final InvocationTargetException e) {
402 
403 				throw new RuntimeException(e.getTargetException());
404 			}
405 
406 			if (value == null) {
407 
408 				return null;
409 			}
410 
411 			return next == null
412 
413 					? value
414 
415 					: next.invoke(value);
416 		}
417 	}
418 
419 	@Nullable
420 	private static Method extractGetter(final Class<?> clazz, final String propertyName) {
421 
422 		for (final Method method : clazz.getMethods()) {
423 
424 			if (method.getParameterTypes().length != 0) {
425 				continue;
426 			}
427 
428 			final String methodName = method.getName();
429 
430 			final String suffix;
431 
432 			if (methodName.startsWith("get")) {
433 
434 				suffix = methodName.substring(3);
435 
436 			} else if (methodName.startsWith("is")) {
437 
438 				suffix = methodName.substring(2);
439 
440 			} else {
441 
442 				continue;
443 			}
444 
445 			if (uncapitalize(suffix).contentEquals(propertyName)) {
446 
447 				return method;
448 			}
449 		}
450 
451 		return null;
452 	}
453 
454 	private static FieldExtractor extractFieldExtractor(final Class<?> clazz, final String propertyName) {
455 
456 		checkNotNull(clazz, "clazz");
457 		checkNotNull(propertyName, "propertyName");
458 
459 		final Method getter = extractGetter(clazz, propertyName);
460 
461 		if (getter != null) {
462 
463 			return new FieldExtractorFromGetter(getter);
464 		}
465 
466 		final String subName = extractSubName(propertyName);
467 
468 		if (subName != null) {
469 
470 			final Method subGetter = extractGetter(clazz, subName);
471 
472 			if (subGetter != null) {
473 
474 				return new FieldExtractorFromGetter(subGetter, //
475 						extractFieldExtractor(subGetter.getReturnType(),
476 								uncapitalize(propertyName.substring(subName.length()))));
477 			}
478 		}
479 
480 		throw new IllegalArgumentException("Cannot find getter for: " + propertyName + ", in: " + clazz.getName());
481 	}
482 
483 	private static String extractSubName(final String propertyName) {
484 
485 		final StringBuilder sb = new StringBuilder();
486 
487 		for (final char c : propertyName.toCharArray()) {
488 
489 			if (!Character.isLowerCase(c)) {
490 
491 				break;
492 			}
493 
494 			sb.append(c);
495 		}
496 
497 		return sb.toString();
498 	}
499 
500 	@FunctionalInterface
501 	public interface Action<T> {
502 
503 		T action() throws ServiceException;
504 	}
505 
506 	@FunctionalInterface
507 	public interface Predicate {
508 
509 		boolean test() throws ServiceException;
510 	}
511 
512 	public static void retryUntil(final int timeoutMs, final Predicate predicate) throws ServiceException {
513 
514 		checkNotNull(predicate, "predicate");
515 
516 		retryUntil(timeoutMs, 1_000, () -> predicate.test() ? "OK" : null);
517 	}
518 
519 	public static <T> T retryUntil(final int timeoutMs, final Action<T> action) throws ServiceException {
520 
521 		return retryUntil(timeoutMs, 1_000, action);
522 	}
523 
524 	public static <T> T retryUntil(final int timeoutMs, final int delayMs, final Action<T> action)
525 			throws ServiceException {
526 
527 		checkNotNull(action, "action");
528 
529 		for (final long startMs = System.currentTimeMillis(); System.currentTimeMillis() < startMs + timeoutMs;) {
530 
531 			final T result = action.action();
532 
533 			if (result != null) {
534 
535 				return result;
536 			}
537 
538 			try {
539 
540 				Thread.sleep(delayMs);
541 
542 			} catch (final InterruptedException e) {
543 
544 				e.printStackTrace();
545 
546 				// do nothing
547 			}
548 		}
549 
550 		System.err.println("A timeout occurs (" + timeoutMs + " ms)");
551 
552 		throw new RuntimeException("Timeout after: " + timeoutMs + " ms");
553 	}
554 }