blob: 2dd43a05cd4fed9c508816ed110af2cc321738af [file] [log] [blame]
Thomas Vachuska24c849c2014-10-27 09:53:05 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2014-present Open Networking Foundation
Thomas Vachuska24c849c2014-10-27 09:53:05 -07003 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07004 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
Thomas Vachuska24c849c2014-10-27 09:53:05 -07007 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07008 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
Thomas Vachuska24c849c2014-10-27 09:53:05 -070015 */
tom5f38b3a2014-08-27 23:50:54 -070016package org.onlab.util;
17
Ray Milkey86ee5e82018-04-02 15:33:07 -070018import com.fasterxml.jackson.databind.ObjectMapper;
19import com.fasterxml.jackson.databind.node.ObjectNode;
Jonathan Hartcc962d82016-08-09 16:52:22 -070020import com.google.common.base.Charsets;
21import com.google.common.base.Strings;
22import com.google.common.collect.Lists;
23import com.google.common.primitives.UnsignedLongs;
24import com.google.common.util.concurrent.ThreadFactoryBuilder;
25import org.slf4j.Logger;
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080026
tom53efab52014-10-07 17:43:48 -070027import java.io.File;
tom53efab52014-10-07 17:43:48 -070028import java.io.IOException;
Ray Milkey86ee5e82018-04-02 15:33:07 -070029import java.io.InputStream;
Madan Jampani27b69c62015-05-15 15:49:02 -070030import java.nio.ByteBuffer;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080031import java.nio.file.FileVisitResult;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080032import java.nio.file.Files;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080033import java.nio.file.Path;
34import java.nio.file.Paths;
35import java.nio.file.SimpleFileVisitor;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080036import java.nio.file.StandardCopyOption;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080037import java.nio.file.attribute.BasicFileAttributes;
Ray Milkeyb68bbbc2017-12-18 10:05:49 -080038import java.security.SecureRandom;
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -070039import java.time.Instant;
40import java.time.OffsetDateTime;
41import java.time.ZoneId;
Madan Jampani27b69c62015-05-15 15:49:02 -070042import java.util.Arrays;
Brian O'Connore2eac102015-02-12 18:30:22 -080043import java.util.Collection;
Thomas Vachuska6519e6f2015-03-11 02:29:31 -070044import java.util.Dictionary;
tom53efab52014-10-07 17:43:48 -070045import java.util.List;
Sho SHIMIZUb5638b82016-02-11 14:55:05 -080046import java.util.Optional;
Thomas Vachuskaadba1522015-06-04 15:08:30 -070047import java.util.Random;
Ray Milkey36992c82015-11-17 13:31:15 -080048import java.util.Set;
Madan Jampani27b69c62015-05-15 15:49:02 -070049import java.util.concurrent.CompletableFuture;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070050import java.util.concurrent.ExecutionException;
Jordan Halterman9bdc24f2017-04-19 23:45:12 -070051import java.util.concurrent.Executor;
Charles Chan9797ebb2020-02-14 13:23:57 -080052import java.util.concurrent.Executors;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070053import java.util.concurrent.Future;
Charles Chan9797ebb2020-02-14 13:23:57 -080054import java.util.concurrent.ScheduledExecutorService;
tom5f38b3a2014-08-27 23:50:54 -070055import java.util.concurrent.ThreadFactory;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070056import java.util.concurrent.TimeUnit;
57import java.util.concurrent.TimeoutException;
Madan Jampani307a21e2016-09-01 15:49:47 -070058import java.util.function.BinaryOperator;
Madan Jampania29c6772015-08-17 13:17:07 -070059import java.util.function.Function;
60import java.util.function.Supplier;
Sho SHIMIZU85803e22016-01-13 21:53:43 -080061import java.util.stream.Collectors;
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -070062import java.util.stream.Stream;
63import java.util.stream.StreamSupport;
tom5f38b3a2014-08-27 23:50:54 -070064
Yuta HIGUCHI47d96092017-11-17 14:05:26 -080065import static com.google.common.base.Preconditions.checkNotNull;
Jonathan Hartcc962d82016-08-09 16:52:22 -070066import static java.nio.file.Files.delete;
67import static java.nio.file.Files.walkFileTree;
68import static org.onlab.util.GroupedThreadFactory.groupedThreadFactory;
69import static org.slf4j.LoggerFactory.getLogger;
Ray Milkey705d9bc2014-11-18 08:19:00 -080070
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080071/**
72 * Miscellaneous utility methods.
73 */
tom5f38b3a2014-08-27 23:50:54 -070074public abstract class Tools {
75
76 private Tools() {
77 }
78
Thomas Vachuska02aeb032015-01-06 22:36:30 -080079 private static final Logger log = getLogger(Tools.class);
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080080
Ray Milkeyb68bbbc2017-12-18 10:05:49 -080081 private static Random random = new SecureRandom();
Thomas Vachuskaadba1522015-06-04 15:08:30 -070082
Ray Milkey86ee5e82018-04-02 15:33:07 -070083 private static final String INPUT_JSON_CANNOT_BE_NULL = "Input JSON cannot be null";
84
Charles Chan9797ebb2020-02-14 13:23:57 -080085 private static ScheduledExecutorService timer = Executors.newScheduledThreadPool(
86 Runtime.getRuntime().availableProcessors(), groupedThreads("onos/tool", "timer"));
87
tom5f38b3a2014-08-27 23:50:54 -070088 /**
89 * Returns a thread factory that produces threads named according to the
90 * supplied name pattern.
91 *
92 * @param pattern name pattern
93 * @return thread factory
94 */
95 public static ThreadFactory namedThreads(String pattern) {
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080096 return new ThreadFactoryBuilder()
97 .setNameFormat(pattern)
Thomas Vachuska480adad2015-03-06 10:27:09 -080098 .setUncaughtExceptionHandler((t, e) -> log.error("Uncaught exception on " + t.getName(), e))
99 .build();
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -0800100 }
Yuta HIGUCHI683e9782014-11-25 17:26:36 -0800101
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -0800102 /**
103 * Returns a thread factory that produces threads named according to the
104 * supplied name pattern and from the specified thread-group. The thread
105 * group name is expected to be specified in slash-delimited format, e.g.
Thomas Vachuskac13b90a2015-02-18 18:19:55 -0800106 * {@code onos/intent}. The thread names will be produced by converting
107 * the thread group name into dash-delimited format and pre-pended to the
108 * specified pattern.
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -0800109 *
110 * @param groupName group name in slash-delimited format to indicate hierarchy
111 * @param pattern name pattern
112 * @return thread factory
113 */
114 public static ThreadFactory groupedThreads(String groupName, String pattern) {
Jian Li03e9fb02016-03-01 17:13:54 -0800115 return groupedThreads(groupName, pattern, log);
116 }
117
118 /**
119 * Returns a thread factory that produces threads named according to the
120 * supplied name pattern and from the specified thread-group. The thread
121 * group name is expected to be specified in slash-delimited format, e.g.
122 * {@code onos/intent}. The thread names will be produced by converting
123 * the thread group name into dash-delimited format and pre-pended to the
124 * specified pattern. If a logger is specified, it will use the logger to
125 * print out the exception if it has any.
126 *
127 * @param groupName group name in slash-delimited format to indicate hierarchy
128 * @param pattern name pattern
129 * @param logger logger
130 * @return thread factory
131 */
132 public static ThreadFactory groupedThreads(String groupName, String pattern, Logger logger) {
133 if (logger == null) {
134 return groupedThreads(groupName, pattern);
135 }
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -0800136 return new ThreadFactoryBuilder()
137 .setThreadFactory(groupedThreadFactory(groupName))
Thomas Vachuskac13b90a2015-02-18 18:19:55 -0800138 .setNameFormat(groupName.replace(GroupedThreadFactory.DELIMITER, "-") + "-" + pattern)
Jian Li03e9fb02016-03-01 17:13:54 -0800139 .setUncaughtExceptionHandler((t, e) -> logger.error("Uncaught exception on " + t.getName(), e))
Thomas Vachuska480adad2015-03-06 10:27:09 -0800140 .build();
tom5f38b3a2014-08-27 23:50:54 -0700141 }
142
tom782a7cf2014-09-11 23:58:38 -0700143 /**
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800144 * Returns a thread factory that produces threads with MIN_PRIORITY.
145 *
146 * @param factory backing ThreadFactory
147 * @return thread factory
148 */
149 public static ThreadFactory minPriority(ThreadFactory factory) {
150 return new ThreadFactoryBuilder()
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800151 .setThreadFactory(factory)
152 .setPriority(Thread.MIN_PRIORITY)
153 .build();
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800154 }
155
156 /**
Yuta HIGUCHIa2a11cd2016-12-19 14:19:11 -0800157 * Returns a thread factory that produces threads with MAX_PRIORITY.
158 *
159 * @param factory backing ThreadFactory
160 * @return thread factory
161 */
162 public static ThreadFactory maxPriority(ThreadFactory factory) {
163 return new ThreadFactoryBuilder()
164 .setThreadFactory(factory)
165 .setPriority(Thread.MAX_PRIORITY)
166 .build();
167 }
168
169 /**
Brian O'Connore2eac102015-02-12 18:30:22 -0800170 * Returns true if the collection is null or is empty.
171 *
172 * @param collection collection to test
173 * @return true if null or empty; false otherwise
174 */
Yuta HIGUCHI488a94c2018-01-26 17:24:09 -0800175 public static boolean isNullOrEmpty(Collection<?> collection) {
Brian O'Connore2eac102015-02-12 18:30:22 -0800176 return collection == null || collection.isEmpty();
177 }
178
179 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700180 * Returns the specified item if that item is not null; otherwise throws
Thomas Vachuskaca88bb72015-04-08 19:38:02 -0700181 * not found exception.
182 *
183 * @param item item to check
184 * @param message not found message
185 * @param <T> item type
186 * @return item if not null
187 * @throws org.onlab.util.ItemNotFoundException if item is null
188 */
189 public static <T> T nullIsNotFound(T item, String message) {
190 if (item == null) {
Andrea Campanella8f6594c2021-10-25 12:33:01 +0200191 log.error(message);
Thomas Vachuskaca88bb72015-04-08 19:38:02 -0700192 throw new ItemNotFoundException(message);
193 }
194 return item;
195 }
196
197 /**
Ray Milkey36992c82015-11-17 13:31:15 -0800198 * Returns the specified set if the set is not null and not empty;
199 * otherwise throws a not found exception.
200 *
201 * @param item set to check
202 * @param message not found message
203 * @param <T> Set item type
204 * @return item if not null and not empty
205 * @throws org.onlab.util.ItemNotFoundException if set is null or empty
206 */
207 public static <T> Set<T> emptyIsNotFound(Set<T> item, String message) {
208 if (item == null || item.isEmpty()) {
Andrea Campanella8f6594c2021-10-25 12:33:01 +0200209 log.error(message);
Ray Milkey36992c82015-11-17 13:31:15 -0800210 throw new ItemNotFoundException(message);
211 }
212 return item;
213 }
214
215 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700216 * Returns the specified item if that item is not null; otherwise throws
217 * bad argument exception.
218 *
219 * @param item item to check
220 * @param message not found message
221 * @param <T> item type
222 * @return item if not null
223 * @throws IllegalArgumentException if item is null
224 */
225 public static <T> T nullIsIllegal(T item, String message) {
226 if (item == null) {
Andrea Campanella8f6594c2021-10-25 12:33:01 +0200227 log.error(message);
Ray Milkeyd43fe452015-05-29 09:35:12 -0700228 throw new IllegalArgumentException(message);
229 }
230 return item;
231 }
232
233 /**
Ray Milkey86ee5e82018-04-02 15:33:07 -0700234 * Utility to convert a mapper and an input stream into a JSON tree,
235 * and be tolerant of a null tree being returned.
236 *
237 * @param mapper JSON object mapper
238 * @param stream IO stream containing the JSON
239 * @return object node for the given
240 * @throws IOException if JSON parsing fails
241 */
242 public static ObjectNode readTreeFromStream(ObjectMapper mapper, InputStream stream) throws IOException {
243 return nullIsIllegal((ObjectNode) mapper.readTree(stream), INPUT_JSON_CANNOT_BE_NULL);
244 }
245
246 /**
tom782a7cf2014-09-11 23:58:38 -0700247 * Converts a string from hex to long.
248 *
249 * @param string hex number in string form; sans 0x
250 * @return long value
251 */
252 public static long fromHex(String string) {
253 return UnsignedLongs.parseUnsignedLong(string, 16);
254 }
255
256 /**
257 * Converts a long value to hex string; 16 wide and sans 0x.
258 *
259 * @param value long value
260 * @return hex string
261 */
262 public static String toHex(long value) {
263 return Strings.padStart(UnsignedLongs.toString(value, 16), 16, '0');
264 }
265
266 /**
267 * Converts a long value to hex string; 16 wide and sans 0x.
268 *
269 * @param value long value
270 * @param width string width; zero padded
271 * @return hex string
272 */
273 public static String toHex(long value, int width) {
274 return Strings.padStart(UnsignedLongs.toString(value, 16), width, '0');
275 }
tomf110fff2014-09-26 00:38:18 -0700276
277 /**
Jonathan Hartcc962d82016-08-09 16:52:22 -0700278 * Returns a string encoding in hex of the given long value with prefix
279 * '0x'.
280 *
281 * @param value long value to encode as hex string
282 * @return hex string
283 */
284 public static String toHexWithPrefix(long value) {
285 return "0x" + Long.toHexString(value);
286 }
287
288 /**
Madan Jampanif2f086c2016-01-13 16:15:39 -0800289 * Returns the UTF-8 encoded byte[] representation of a String.
Jian Lidfba7392016-01-22 16:46:58 -0800290 * @param input input string
291 * @return UTF-8 encoded byte array
Madan Jampanif2f086c2016-01-13 16:15:39 -0800292 */
293 public static byte[] getBytesUtf8(String input) {
294 return input.getBytes(Charsets.UTF_8);
295 }
296
297 /**
298 * Returns the String representation of UTF-8 encoded byte[].
Jian Lidfba7392016-01-22 16:46:58 -0800299 * @param input input byte array
300 * @return UTF-8 encoded string
Madan Jampanif2f086c2016-01-13 16:15:39 -0800301 */
302 public static String toStringUtf8(byte[] input) {
303 return new String(input, Charsets.UTF_8);
304 }
305
306 /**
Madan Jampani9eb55d12015-08-14 07:47:56 -0700307 * Returns a copy of the input byte array.
308 *
309 * @param original input
310 * @return copy of original
311 */
312 public static byte[] copyOf(byte[] original) {
313 return Arrays.copyOf(original, original.length);
314 }
315
316 /**
Thomas Vachuska6519e6f2015-03-11 02:29:31 -0700317 * Get property as a string value.
318 *
319 * @param properties properties to be looked up
320 * @param propertyName the name of the property to look up
321 * @return value when the propertyName is defined or return null
322 */
323 public static String get(Dictionary<?, ?> properties, String propertyName) {
324 Object v = properties.get(propertyName);
325 String s = (v instanceof String) ? (String) v :
326 v != null ? v.toString() : null;
327 return Strings.isNullOrEmpty(s) ? null : s.trim();
328 }
329
330 /**
Jian Lid9b5f552016-03-11 18:15:31 -0800331 * Get Integer property from the propertyName
332 * Return null if propertyName is not found.
333 *
334 * @param properties properties to be looked up
335 * @param propertyName the name of the property to look up
336 * @return value when the propertyName is defined or return null
337 */
338 public static Integer getIntegerProperty(Dictionary<?, ?> properties,
339 String propertyName) {
340 Integer value;
341 try {
342 String s = get(properties, propertyName);
343 value = Strings.isNullOrEmpty(s) ? null : Integer.valueOf(s);
344 } catch (NumberFormatException | ClassCastException e) {
345 value = null;
346 }
347 return value;
348 }
349
350 /**
351 * Get Integer property from the propertyName
352 * Return default value if propertyName is not found.
353 *
354 * @param properties properties to be looked up
355 * @param propertyName the name of the property to look up
356 * @param defaultValue the default value that to be assigned
357 * @return value when the propertyName is defined or return default value
358 */
359 public static int getIntegerProperty(Dictionary<?, ?> properties,
360 String propertyName,
361 int defaultValue) {
362 try {
363 String s = get(properties, propertyName);
364 return Strings.isNullOrEmpty(s) ? defaultValue : Integer.valueOf(s);
365 } catch (NumberFormatException | ClassCastException e) {
366 return defaultValue;
367 }
368 }
369
370 /**
371 * Check property name is defined and set to true.
372 *
373 * @param properties properties to be looked up
374 * @param propertyName the name of the property to look up
375 * @return value when the propertyName is defined or return null
376 */
377 public static Boolean isPropertyEnabled(Dictionary<?, ?> properties,
378 String propertyName) {
379 Boolean value;
380 try {
381 String s = get(properties, propertyName);
382 value = Strings.isNullOrEmpty(s) ? null : Boolean.valueOf(s);
383 } catch (ClassCastException e) {
384 value = null;
385 }
386 return value;
387 }
388
389 /**
390 * Check property name is defined as set to true.
391 *
392 * @param properties properties to be looked up
393 * @param propertyName the name of the property to look up
394 * @param defaultValue the default value that to be assigned
395 * @return value when the propertyName is defined or return the default value
396 */
397 public static boolean isPropertyEnabled(Dictionary<?, ?> properties,
398 String propertyName,
399 boolean defaultValue) {
400 try {
401 String s = get(properties, propertyName);
402 return Strings.isNullOrEmpty(s) ? defaultValue : Boolean.valueOf(s);
403 } catch (ClassCastException e) {
404 return defaultValue;
405 }
406 }
407
408 /**
tomf110fff2014-09-26 00:38:18 -0700409 * Suspends the current thread for a specified number of millis.
410 *
411 * @param ms number of millis
412 */
413 public static void delay(int ms) {
414 try {
415 Thread.sleep(ms);
416 } catch (InterruptedException e) {
Ray Milkey5c7d4882018-02-05 14:50:39 -0800417 Thread.currentThread().interrupt();
Ray Milkey986a47a2018-01-25 11:38:51 -0800418 throw new IllegalStateException("Interrupted", e);
tomf110fff2014-09-26 00:38:18 -0700419 }
420 }
421
tom53efab52014-10-07 17:43:48 -0700422 /**
sdn94b00152016-08-30 02:12:32 -0700423 * Get Long property from the propertyName
424 * Return null if propertyName is not found.
425 *
426 * @param properties properties to be looked up
427 * @param propertyName the name of the property to look up
428 * @return value when the propertyName is defined or return null
429 */
430 public static Long getLongProperty(Dictionary<?, ?> properties,
431 String propertyName) {
432 Long value;
433 try {
434 String s = get(properties, propertyName);
435 value = Strings.isNullOrEmpty(s) ? null : Long.valueOf(s);
436 } catch (NumberFormatException | ClassCastException e) {
437 value = null;
438 }
439 return value;
440 }
441
442 /**
Georgios Katsikas5e2d40f2018-07-29 18:28:01 +0200443 * Get Float property from the propertyName
444 * Return null if propertyName is not found.
445 *
446 * @param properties properties to be looked up
447 * @param propertyName the name of the property to look up
448 * @return value when the propertyName is defined or return null
449 */
450 public static Float getFloatProperty(Dictionary<?, ?> properties,
451 String propertyName) {
452 Float value;
453 try {
454 String s = get(properties, propertyName);
455 value = Strings.isNullOrEmpty(s) ? null : Float.valueOf(s);
456 } catch (NumberFormatException | ClassCastException e) {
457 value = null;
458 }
459 return value;
460 }
461
462 /**
Madan Jampania29c6772015-08-17 13:17:07 -0700463 * Returns a function that retries execution on failure.
464 * @param base base function
465 * @param exceptionClass type of exception for which to retry
466 * @param maxRetries max number of retries before giving up
467 * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from
468 * the interval (0, maxDelayBetweenRetries]
469 * @return function
Thomas Vachuska87ae1d92015-08-19 17:39:11 -0700470 * @param <U> type of function input
471 * @param <V> type of function output
Madan Jampania29c6772015-08-17 13:17:07 -0700472 */
473 public static <U, V> Function<U, V> retryable(Function<U, V> base,
474 Class<? extends Throwable> exceptionClass,
475 int maxRetries,
476 int maxDelayBetweenRetries) {
477 return new RetryingFunction<>(base, exceptionClass, maxRetries, maxDelayBetweenRetries);
478 }
479
480 /**
481 * Returns a Supplier that retries execution on failure.
482 * @param base base supplier
483 * @param exceptionClass type of exception for which to retry
484 * @param maxRetries max number of retries before giving up
485 * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from
486 * the interval (0, maxDelayBetweenRetries]
487 * @return supplier
Thomas Vachuska87ae1d92015-08-19 17:39:11 -0700488 * @param <V> type of supplied result
Madan Jampania29c6772015-08-17 13:17:07 -0700489 */
490 public static <V> Supplier<V> retryable(Supplier<V> base,
491 Class<? extends Throwable> exceptionClass,
492 int maxRetries,
493 int maxDelayBetweenRetries) {
494 return () -> new RetryingFunction<>(v -> base.get(),
495 exceptionClass,
496 maxRetries,
497 maxDelayBetweenRetries).apply(null);
498 }
499
500 /**
Thomas Vachuskaadba1522015-06-04 15:08:30 -0700501 * Suspends the current thread for a random number of millis between 0 and
502 * the indicated limit.
503 *
504 * @param ms max number of millis
505 */
506 public static void randomDelay(int ms) {
507 try {
508 Thread.sleep(random.nextInt(ms));
509 } catch (InterruptedException e) {
Ray Milkey5c7d4882018-02-05 14:50:39 -0800510 Thread.currentThread().interrupt();
Ray Milkey986a47a2018-01-25 11:38:51 -0800511 throw new IllegalStateException("Interrupted", e);
Thomas Vachuskaadba1522015-06-04 15:08:30 -0700512 }
513 }
514
515 /**
Thomas Vachuskac40d4632015-04-09 16:55:03 -0700516 * Suspends the current thread for a specified number of millis and nanos.
517 *
518 * @param ms number of millis
519 * @param nanos number of nanos
520 */
521 public static void delay(int ms, int nanos) {
522 try {
523 Thread.sleep(ms, nanos);
524 } catch (InterruptedException e) {
Ray Milkey5c7d4882018-02-05 14:50:39 -0800525 Thread.currentThread().interrupt();
Ray Milkey986a47a2018-01-25 11:38:51 -0800526 throw new IllegalStateException("Interrupted", e);
Thomas Vachuskac40d4632015-04-09 16:55:03 -0700527 }
528 }
529
530 /**
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800531 * Purges the specified directory path.&nbsp;Use with great caution since
532 * no attempt is made to check for symbolic links, which could result in
533 * deletion of unintended files.
534 *
535 * @param path directory to be removed
536 * @throws java.io.IOException if unable to remove contents
537 */
538 public static void removeDirectory(String path) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800539 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700540 File dir = new File(path);
541 if (dir.exists() && dir.isDirectory()) {
542 walkFileTree(Paths.get(path), visitor);
543 if (visitor.exception != null) {
544 throw visitor.exception;
545 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800546 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800547 }
548
549 /**
550 * Purges the specified directory path.&nbsp;Use with great caution since
551 * no attempt is made to check for symbolic links, which could result in
552 * deletion of unintended files.
553 *
554 * @param dir directory to be removed
555 * @throws java.io.IOException if unable to remove contents
556 */
557 public static void removeDirectory(File dir) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800558 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700559 if (dir.exists() && dir.isDirectory()) {
560 walkFileTree(Paths.get(dir.getAbsolutePath()), visitor);
561 if (visitor.exception != null) {
562 throw visitor.exception;
563 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800564 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800565 }
566
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800567 // Auxiliary path visitor for recursive directory structure removal.
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800568 private static class DirectoryDeleter extends SimpleFileVisitor<Path> {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800569
570 private IOException exception;
571
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800572 @Override
573 public FileVisitResult visitFile(Path file, BasicFileAttributes attributes)
574 throws IOException {
575 if (attributes.isRegularFile()) {
576 delete(file);
577 }
578 return FileVisitResult.CONTINUE;
579 }
580
581 @Override
582 public FileVisitResult postVisitDirectory(Path directory, IOException ioe)
583 throws IOException {
584 delete(directory);
585 return FileVisitResult.CONTINUE;
586 }
587
588 @Override
589 public FileVisitResult visitFileFailed(Path file, IOException ioe)
590 throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800591 this.exception = ioe;
592 return FileVisitResult.TERMINATE;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800593 }
594 }
595
Madan Jampani30a57f82015-03-02 12:19:41 -0800596 /**
597 * Returns a human friendly time ago string for a specified system time.
Thomas Vachuska480adad2015-03-06 10:27:09 -0800598 *
Madan Jampani30a57f82015-03-02 12:19:41 -0800599 * @param unixTime system time in millis
600 * @return human friendly time ago
601 */
602 public static String timeAgo(long unixTime) {
603 long deltaMillis = System.currentTimeMillis() - unixTime;
604 long secondsSince = (long) (deltaMillis / 1000.0);
605 long minsSince = (long) (deltaMillis / (1000.0 * 60));
606 long hoursSince = (long) (deltaMillis / (1000.0 * 60 * 60));
607 long daysSince = (long) (deltaMillis / (1000.0 * 60 * 60 * 24));
608 if (daysSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800609 return String.format("%dd%dh ago", daysSince, hoursSince - daysSince * 24);
Madan Jampani30a57f82015-03-02 12:19:41 -0800610 } else if (hoursSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800611 return String.format("%dh%dm ago", hoursSince, minsSince - hoursSince * 60);
Madan Jampani30a57f82015-03-02 12:19:41 -0800612 } else if (minsSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800613 return String.format("%dm%ds ago", minsSince, secondsSince - minsSince * 60);
Madan Jampani30a57f82015-03-02 12:19:41 -0800614 } else if (secondsSince > 0) {
615 return String.format("%ds ago", secondsSince);
616 } else {
617 return "just now";
618 }
619 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800620
621 /**
622 * Copies the specified directory path.&nbsp;Use with great caution since
623 * no attempt is made to check for symbolic links, which could result in
624 * copy of unintended files.
625 *
626 * @param src directory to be copied
627 * @param dst destination directory to be removed
628 * @throws java.io.IOException if unable to remove contents
629 */
630 public static void copyDirectory(String src, String dst) throws IOException {
631 walkFileTree(Paths.get(src), new DirectoryCopier(src, dst));
632 }
633
634 /**
635 * Copies the specified directory path.&nbsp;Use with great caution since
636 * no attempt is made to check for symbolic links, which could result in
637 * copy of unintended files.
638 *
639 * @param src directory to be copied
640 * @param dst destination directory to be removed
641 * @throws java.io.IOException if unable to remove contents
642 */
643 public static void copyDirectory(File src, File dst) throws IOException {
644 walkFileTree(Paths.get(src.getAbsolutePath()),
645 new DirectoryCopier(src.getAbsolutePath(),
646 dst.getAbsolutePath()));
647 }
648
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700649 /**
650 * Returns the future value when complete or if future
651 * completes exceptionally returns the defaultValue.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700652 *
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700653 * @param future future
654 * @param defaultValue default value
655 * @param <T> future value type
656 * @return future value when complete or if future
657 * completes exceptionally returns the defaultValue.
658 */
659 public static <T> T futureGetOrElse(Future<T> future, T defaultValue) {
660 try {
661 return future.get();
662 } catch (InterruptedException e) {
663 Thread.currentThread().interrupt();
664 return defaultValue;
665 } catch (ExecutionException e) {
666 return defaultValue;
667 }
668 }
669
670 /**
671 * Returns the future value when complete or if future
672 * completes exceptionally returns the defaultValue.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700673 *
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700674 * @param future future
675 * @param timeout time to wait for successful completion
676 * @param timeUnit time unit
677 * @param defaultValue default value
678 * @param <T> future value type
679 * @return future value when complete or if future
680 * completes exceptionally returns the defaultValue.
681 */
682 public static <T> T futureGetOrElse(Future<T> future,
683 long timeout,
684 TimeUnit timeUnit,
685 T defaultValue) {
686 try {
687 return future.get(timeout, timeUnit);
688 } catch (InterruptedException e) {
689 Thread.currentThread().interrupt();
690 return defaultValue;
691 } catch (ExecutionException | TimeoutException e) {
692 return defaultValue;
693 }
694 }
695
Madan Jampani27b69c62015-05-15 15:49:02 -0700696 /**
697 * Returns a future that is completed exceptionally.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700698 *
Madan Jampani27b69c62015-05-15 15:49:02 -0700699 * @param t exception
700 * @param <T> future value type
701 * @return future
702 */
703 public static <T> CompletableFuture<T> exceptionalFuture(Throwable t) {
704 CompletableFuture<T> future = new CompletableFuture<>();
705 future.completeExceptionally(t);
706 return future;
707 }
708
709 /**
Charles Chan9797ebb2020-02-14 13:23:57 -0800710 * Returns a future that completes normally after given time period.
711 *
712 * @param timeout amount of time to wait before completing the future
713 * @param unit Time unit
714 * @return a future that completes after given time period
715 */
716 public static CompletableFuture<Void> completeAfter(long timeout, TimeUnit unit) {
717 CompletableFuture<Void> result = new CompletableFuture<>();
718 timer.schedule(() -> result.complete(null), timeout, unit);
719 return result;
720 }
721
722 /**
Jordan Halterman046faeb2017-05-01 15:10:13 -0700723 * Returns a future that's completed using the given {@code orderedExecutor} if the future is not blocked or the
724 * given {@code threadPoolExecutor} if the future is blocked.
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700725 * <p>
Jordan Halterman046faeb2017-05-01 15:10:13 -0700726 * This method allows futures to maintain single-thread semantics via the provided {@code orderedExecutor} while
727 * ensuring user code can block without blocking completion of futures. When the returned future or any of its
728 * descendants is blocked on a {@link CompletableFuture#get()} or {@link CompletableFuture#join()} call, completion
729 * of the returned future will be done using the provided {@code threadPoolExecutor}.
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700730 *
731 * @param future the future to convert into an asynchronous future
Jordan Halterman046faeb2017-05-01 15:10:13 -0700732 * @param orderedExecutor the ordered executor with which to attempt to complete the future
733 * @param threadPoolExecutor the backup executor with which to complete blocked futures
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700734 * @param <T> future value type
735 * @return a new completable future to be completed using the provided {@code executor} once the provided
736 * {@code future} is complete
737 */
Jordan Halterman046faeb2017-05-01 15:10:13 -0700738 public static <T> CompletableFuture<T> orderedFuture(
739 CompletableFuture<T> future,
740 Executor orderedExecutor,
741 Executor threadPoolExecutor) {
Jordan Haltermane265d372017-05-17 22:40:47 -0700742 if (future.isDone()) {
743 return future;
744 }
745
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700746 BlockingAwareFuture<T> newFuture = new BlockingAwareFuture<>();
Jordan Halterman046faeb2017-05-01 15:10:13 -0700747 future.whenComplete((result, error) -> {
748 Runnable completer = () -> {
749 if (future.isCompletedExceptionally()) {
750 newFuture.completeExceptionally(error);
751 } else {
752 newFuture.complete(result);
753 }
754 };
755
756 if (newFuture.isBlocked()) {
757 threadPoolExecutor.execute(completer);
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700758 } else {
Jordan Halterman046faeb2017-05-01 15:10:13 -0700759 orderedExecutor.execute(completer);
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700760 }
Jordan Halterman046faeb2017-05-01 15:10:13 -0700761 });
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700762 return newFuture;
763 }
764
765 /**
Sho SHIMIZU85803e22016-01-13 21:53:43 -0800766 * Returns a new CompletableFuture completed with a list of computed values
767 * when all of the given CompletableFuture complete.
768 *
769 * @param futures the CompletableFutures
770 * @param <T> value type of CompletableFuture
771 * @return a new CompletableFuture that is completed when all of the given CompletableFutures complete
772 */
773 public static <T> CompletableFuture<List<T>> allOf(List<CompletableFuture<T>> futures) {
774 return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]))
775 .thenApply(v -> futures.stream()
776 .map(CompletableFuture::join)
777 .collect(Collectors.toList())
778 );
779 }
780
781 /**
Madan Jampani307a21e2016-09-01 15:49:47 -0700782 * Returns a new CompletableFuture completed by reducing a list of computed values
783 * when all of the given CompletableFuture complete.
784 *
785 * @param futures the CompletableFutures
786 * @param reducer reducer for computing the result
787 * @param emptyValue zero value to be returned if the input future list is empty
788 * @param <T> value type of CompletableFuture
789 * @return a new CompletableFuture that is completed when all of the given CompletableFutures complete
790 */
791 public static <T> CompletableFuture<T> allOf(List<CompletableFuture<T>> futures,
792 BinaryOperator<T> reducer,
793 T emptyValue) {
794 return Tools.allOf(futures)
795 .thenApply(resultList -> resultList.stream().reduce(reducer).orElse(emptyValue));
796 }
797
798 /**
Jordan Halterman281dbf32018-06-15 17:46:28 -0700799 * Returns a new CompletableFuture completed with the first result from a list of futures. If no future
800 * is completed successfully, the returned future will be completed with the first exception.
801 *
802 * @param futures the input futures
803 * @param <T> future result type
804 * @return a new CompletableFuture
805 */
806 public static <T> CompletableFuture<T> firstOf(List<CompletableFuture<T>> futures) {
807 CompletableFuture<T> resultFuture = new CompletableFuture<>();
808 CompletableFuture.allOf(futures.stream()
809 .map(future -> future.thenAccept(r -> resultFuture.complete(r)))
810 .toArray(CompletableFuture[]::new))
811 .whenComplete((r, e) -> {
812 if (!resultFuture.isDone()) {
813 if (e != null) {
814 resultFuture.completeExceptionally(e);
815 } else {
816 resultFuture.complete(null);
817 }
818 }
819 });
820 return resultFuture;
821 }
822
823 /**
Madan Jampani307a21e2016-09-01 15:49:47 -0700824 * Returns a new CompletableFuture completed by with the first positive result from a list of
825 * input CompletableFutures.
826 *
827 * @param futures the input list of CompletableFutures
828 * @param positiveResultMatcher matcher to identify a positive result
829 * @param negativeResult value to complete with if none of the futures complete with a positive result
830 * @param <T> value type of CompletableFuture
831 * @return a new CompletableFuture
832 */
833 public static <T> CompletableFuture<T> firstOf(List<CompletableFuture<T>> futures,
834 Match<T> positiveResultMatcher,
835 T negativeResult) {
836 CompletableFuture<T> responseFuture = new CompletableFuture<>();
837 Tools.allOf(Lists.transform(futures, future -> future.thenAccept(r -> {
838 if (positiveResultMatcher.matches(r)) {
839 responseFuture.complete(r);
840 }
841 }))).whenComplete((r, e) -> {
842 if (!responseFuture.isDone()) {
843 if (e != null) {
844 responseFuture.completeExceptionally(e);
845 } else {
846 responseFuture.complete(negativeResult);
847 }
848 }
849 });
850 return responseFuture;
851 }
852
853 /**
Madan Jampani27b69c62015-05-15 15:49:02 -0700854 * Returns the contents of {@code ByteBuffer} as byte array.
855 * <p>
856 * WARNING: There is a performance cost due to array copy
857 * when using this method.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700858 *
Madan Jampani27b69c62015-05-15 15:49:02 -0700859 * @param buffer byte buffer
860 * @return byte array containing the byte buffer contents
861 */
862 public static byte[] byteBuffertoArray(ByteBuffer buffer) {
863 int length = buffer.remaining();
864 if (buffer.hasArray()) {
865 int offset = buffer.arrayOffset() + buffer.position();
866 return Arrays.copyOfRange(buffer.array(), offset, offset + length);
867 }
868 byte[] bytes = new byte[length];
869 buffer.duplicate().get(bytes);
870 return bytes;
871 }
872
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700873 /**
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700874 * Converts an iterable to a stream.
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700875 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700876 * @param it iterable to convert
877 * @param <T> type if item
878 * @return iterable as a stream
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700879 */
880 public static <T> Stream<T> stream(Iterable<T> it) {
881 return StreamSupport.stream(it.spliterator(), false);
882 }
883
Sho SHIMIZUb5638b82016-02-11 14:55:05 -0800884 /**
885 * Converts an optional to a stream.
886 *
887 * @param optional optional to convert
888 * @param <T> type of enclosed value
889 * @return optional as a stream
890 */
Sho SHIMIZU6ac20982016-05-04 09:50:54 -0700891 public static <T> Stream<T> stream(Optional<? extends T> optional) {
HIGUCHI Yuta0bc256f2016-05-06 15:28:26 -0700892 return optional.map(x -> Stream.<T>of(x)).orElse(Stream.empty());
Sho SHIMIZUb5638b82016-02-11 14:55:05 -0800893 }
894
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800895 // Auxiliary path visitor for recursive directory structure copying.
896 private static class DirectoryCopier extends SimpleFileVisitor<Path> {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800897 private Path src;
898 private Path dst;
899 private StandardCopyOption copyOption = StandardCopyOption.REPLACE_EXISTING;
900
901 DirectoryCopier(String src, String dst) {
902 this.src = Paths.get(src);
903 this.dst = Paths.get(dst);
904 }
905
906 @Override
907 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
908 Path targetPath = dst.resolve(src.relativize(dir));
909 if (!Files.exists(targetPath)) {
910 Files.createDirectory(targetPath);
911 }
912 return FileVisitResult.CONTINUE;
913 }
914
915 @Override
916 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
917 Files.copy(file, dst.resolve(src.relativize(file)), copyOption);
918 return FileVisitResult.CONTINUE;
919 }
920 }
921
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700922 /**
923 * Creates OffsetDateTime instance from epoch milliseconds,
924 * using system default time zone.
925 *
926 * @param epochMillis to convert
927 * @return OffsetDateTime
928 */
929 public static OffsetDateTime defaultOffsetDataTime(long epochMillis) {
930 return OffsetDateTime.ofInstant(Instant.ofEpochMilli(epochMillis),
931 ZoneId.systemDefault());
932 }
933
Yuta HIGUCHI47d96092017-11-17 14:05:26 -0800934 /**
935 * Returns smaller of the two Comparable values.
936 *
937 * @param l an argument
938 * @param r another argument
939 * @return the smaller of {@code l} or {@code r}
940 * @param <C> Comparable type
941 * @throws NullPointerException if any of the arguments were null.
942 */
943 public static <C extends Comparable<? super C>> C min(C l, C r) {
944 checkNotNull(l, "l cannot be null");
945 checkNotNull(r, "r cannot be null");
946 return l.compareTo(r) <= 0 ? l : r;
947 }
948
949 /**
950 * Returns larger of the two Comparable values.
951 *
952 * @param l an argument
953 * @param r another argument
954 * @return the larger of {@code l} or {@code r}
955 * @param <C> Comparable type
956 * @throws NullPointerException if any of the arguments were null.
957 */
958 public static <C extends Comparable<? super C>> C max(C l, C r) {
959 checkNotNull(l, "l cannot be null");
960 checkNotNull(r, "r cannot be null");
961 return l.compareTo(r) >= 0 ? l : r;
962 }
Charles Chan58a33da2018-04-04 17:26:46 -0700963
964 /**
965 * Log level for the customized logger.
966 */
967 public enum LogLevel {
968 TRACE, DEBUG, INFO, WARN, ERROR
969 }
970
971 /**
972 * Wrapper function that enables logger invocation with log level as a parameter.
973 *
974 * @param logger logger
975 * @param level log level
976 * @param format format string
977 * @param args objects
978 */
979 public static void log(Logger logger, LogLevel level, String format, Object... args) {
980 switch (level) {
981 case TRACE:
982 logger.trace(format, args);
983 break;
984 case DEBUG:
985 logger.debug(format, args);
986 break;
987 case INFO:
988 logger.info(format, args);
989 break;
990 case WARN:
991 logger.warn(format, args);
992 break;
993 case ERROR:
994 logger.error(format, args);
995 break;
996 default:
997 log.error("Unknown log level {}", level);
998 break;
999 }
1000 }
tom5f38b3a2014-08-27 23:50:54 -07001001}