blob: e0ea0d5eef822caa1c53c9aa767385c500295c84 [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;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070052import java.util.concurrent.Future;
tom5f38b3a2014-08-27 23:50:54 -070053import java.util.concurrent.ThreadFactory;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070054import java.util.concurrent.TimeUnit;
55import java.util.concurrent.TimeoutException;
Madan Jampani307a21e2016-09-01 15:49:47 -070056import java.util.function.BinaryOperator;
Madan Jampania29c6772015-08-17 13:17:07 -070057import java.util.function.Function;
58import java.util.function.Supplier;
Sho SHIMIZU85803e22016-01-13 21:53:43 -080059import java.util.stream.Collectors;
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -070060import java.util.stream.Stream;
61import java.util.stream.StreamSupport;
tom5f38b3a2014-08-27 23:50:54 -070062
Yuta HIGUCHI47d96092017-11-17 14:05:26 -080063import static com.google.common.base.Preconditions.checkNotNull;
Jonathan Hartcc962d82016-08-09 16:52:22 -070064import static java.nio.file.Files.delete;
65import static java.nio.file.Files.walkFileTree;
66import static org.onlab.util.GroupedThreadFactory.groupedThreadFactory;
67import static org.slf4j.LoggerFactory.getLogger;
Ray Milkey705d9bc2014-11-18 08:19:00 -080068
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080069/**
70 * Miscellaneous utility methods.
71 */
tom5f38b3a2014-08-27 23:50:54 -070072public abstract class Tools {
73
74 private Tools() {
75 }
76
Thomas Vachuska02aeb032015-01-06 22:36:30 -080077 private static final Logger log = getLogger(Tools.class);
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080078
Ray Milkeyb68bbbc2017-12-18 10:05:49 -080079 private static Random random = new SecureRandom();
Thomas Vachuskaadba1522015-06-04 15:08:30 -070080
Ray Milkey86ee5e82018-04-02 15:33:07 -070081 private static final String INPUT_JSON_CANNOT_BE_NULL = "Input JSON cannot be null";
82
tom5f38b3a2014-08-27 23:50:54 -070083 /**
84 * Returns a thread factory that produces threads named according to the
85 * supplied name pattern.
86 *
87 * @param pattern name pattern
88 * @return thread factory
89 */
90 public static ThreadFactory namedThreads(String pattern) {
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080091 return new ThreadFactoryBuilder()
92 .setNameFormat(pattern)
Thomas Vachuska480adad2015-03-06 10:27:09 -080093 .setUncaughtExceptionHandler((t, e) -> log.error("Uncaught exception on " + t.getName(), e))
94 .build();
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080095 }
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080096
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080097 /**
98 * Returns a thread factory that produces threads named according to the
99 * supplied name pattern and from the specified thread-group. The thread
100 * group name is expected to be specified in slash-delimited format, e.g.
Thomas Vachuskac13b90a2015-02-18 18:19:55 -0800101 * {@code onos/intent}. The thread names will be produced by converting
102 * the thread group name into dash-delimited format and pre-pended to the
103 * specified pattern.
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -0800104 *
105 * @param groupName group name in slash-delimited format to indicate hierarchy
106 * @param pattern name pattern
107 * @return thread factory
108 */
109 public static ThreadFactory groupedThreads(String groupName, String pattern) {
Jian Li03e9fb02016-03-01 17:13:54 -0800110 return groupedThreads(groupName, pattern, log);
111 }
112
113 /**
114 * Returns a thread factory that produces threads named according to the
115 * supplied name pattern and from the specified thread-group. The thread
116 * group name is expected to be specified in slash-delimited format, e.g.
117 * {@code onos/intent}. The thread names will be produced by converting
118 * the thread group name into dash-delimited format and pre-pended to the
119 * specified pattern. If a logger is specified, it will use the logger to
120 * print out the exception if it has any.
121 *
122 * @param groupName group name in slash-delimited format to indicate hierarchy
123 * @param pattern name pattern
124 * @param logger logger
125 * @return thread factory
126 */
127 public static ThreadFactory groupedThreads(String groupName, String pattern, Logger logger) {
128 if (logger == null) {
129 return groupedThreads(groupName, pattern);
130 }
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -0800131 return new ThreadFactoryBuilder()
132 .setThreadFactory(groupedThreadFactory(groupName))
Thomas Vachuskac13b90a2015-02-18 18:19:55 -0800133 .setNameFormat(groupName.replace(GroupedThreadFactory.DELIMITER, "-") + "-" + pattern)
Jian Li03e9fb02016-03-01 17:13:54 -0800134 .setUncaughtExceptionHandler((t, e) -> logger.error("Uncaught exception on " + t.getName(), e))
Thomas Vachuska480adad2015-03-06 10:27:09 -0800135 .build();
tom5f38b3a2014-08-27 23:50:54 -0700136 }
137
tom782a7cf2014-09-11 23:58:38 -0700138 /**
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800139 * Returns a thread factory that produces threads with MIN_PRIORITY.
140 *
141 * @param factory backing ThreadFactory
142 * @return thread factory
143 */
144 public static ThreadFactory minPriority(ThreadFactory factory) {
145 return new ThreadFactoryBuilder()
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800146 .setThreadFactory(factory)
147 .setPriority(Thread.MIN_PRIORITY)
148 .build();
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800149 }
150
151 /**
Yuta HIGUCHIa2a11cd2016-12-19 14:19:11 -0800152 * Returns a thread factory that produces threads with MAX_PRIORITY.
153 *
154 * @param factory backing ThreadFactory
155 * @return thread factory
156 */
157 public static ThreadFactory maxPriority(ThreadFactory factory) {
158 return new ThreadFactoryBuilder()
159 .setThreadFactory(factory)
160 .setPriority(Thread.MAX_PRIORITY)
161 .build();
162 }
163
164 /**
Brian O'Connore2eac102015-02-12 18:30:22 -0800165 * Returns true if the collection is null or is empty.
166 *
167 * @param collection collection to test
168 * @return true if null or empty; false otherwise
169 */
Yuta HIGUCHI488a94c2018-01-26 17:24:09 -0800170 public static boolean isNullOrEmpty(Collection<?> collection) {
Brian O'Connore2eac102015-02-12 18:30:22 -0800171 return collection == null || collection.isEmpty();
172 }
173
174 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700175 * Returns the specified item if that item is not null; otherwise throws
Thomas Vachuskaca88bb72015-04-08 19:38:02 -0700176 * not found exception.
177 *
178 * @param item item to check
179 * @param message not found message
180 * @param <T> item type
181 * @return item if not null
182 * @throws org.onlab.util.ItemNotFoundException if item is null
183 */
184 public static <T> T nullIsNotFound(T item, String message) {
185 if (item == null) {
186 throw new ItemNotFoundException(message);
187 }
188 return item;
189 }
190
191 /**
Ray Milkey36992c82015-11-17 13:31:15 -0800192 * Returns the specified set if the set is not null and not empty;
193 * otherwise throws a not found exception.
194 *
195 * @param item set to check
196 * @param message not found message
197 * @param <T> Set item type
198 * @return item if not null and not empty
199 * @throws org.onlab.util.ItemNotFoundException if set is null or empty
200 */
201 public static <T> Set<T> emptyIsNotFound(Set<T> item, String message) {
202 if (item == null || item.isEmpty()) {
203 throw new ItemNotFoundException(message);
204 }
205 return item;
206 }
207
208 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700209 * Returns the specified item if that item is not null; otherwise throws
210 * bad argument exception.
211 *
212 * @param item item to check
213 * @param message not found message
214 * @param <T> item type
215 * @return item if not null
216 * @throws IllegalArgumentException if item is null
217 */
218 public static <T> T nullIsIllegal(T item, String message) {
219 if (item == null) {
220 throw new IllegalArgumentException(message);
221 }
222 return item;
223 }
224
225 /**
Ray Milkey86ee5e82018-04-02 15:33:07 -0700226 * Utility to convert a mapper and an input stream into a JSON tree,
227 * and be tolerant of a null tree being returned.
228 *
229 * @param mapper JSON object mapper
230 * @param stream IO stream containing the JSON
231 * @return object node for the given
232 * @throws IOException if JSON parsing fails
233 */
234 public static ObjectNode readTreeFromStream(ObjectMapper mapper, InputStream stream) throws IOException {
235 return nullIsIllegal((ObjectNode) mapper.readTree(stream), INPUT_JSON_CANNOT_BE_NULL);
236 }
237
238 /**
tom782a7cf2014-09-11 23:58:38 -0700239 * Converts a string from hex to long.
240 *
241 * @param string hex number in string form; sans 0x
242 * @return long value
243 */
244 public static long fromHex(String string) {
245 return UnsignedLongs.parseUnsignedLong(string, 16);
246 }
247
248 /**
249 * Converts a long value to hex string; 16 wide and sans 0x.
250 *
251 * @param value long value
252 * @return hex string
253 */
254 public static String toHex(long value) {
255 return Strings.padStart(UnsignedLongs.toString(value, 16), 16, '0');
256 }
257
258 /**
259 * Converts a long value to hex string; 16 wide and sans 0x.
260 *
261 * @param value long value
262 * @param width string width; zero padded
263 * @return hex string
264 */
265 public static String toHex(long value, int width) {
266 return Strings.padStart(UnsignedLongs.toString(value, 16), width, '0');
267 }
tomf110fff2014-09-26 00:38:18 -0700268
269 /**
Jonathan Hartcc962d82016-08-09 16:52:22 -0700270 * Returns a string encoding in hex of the given long value with prefix
271 * '0x'.
272 *
273 * @param value long value to encode as hex string
274 * @return hex string
275 */
276 public static String toHexWithPrefix(long value) {
277 return "0x" + Long.toHexString(value);
278 }
279
280 /**
Madan Jampanif2f086c2016-01-13 16:15:39 -0800281 * Returns the UTF-8 encoded byte[] representation of a String.
Jian Lidfba7392016-01-22 16:46:58 -0800282 * @param input input string
283 * @return UTF-8 encoded byte array
Madan Jampanif2f086c2016-01-13 16:15:39 -0800284 */
285 public static byte[] getBytesUtf8(String input) {
286 return input.getBytes(Charsets.UTF_8);
287 }
288
289 /**
290 * Returns the String representation of UTF-8 encoded byte[].
Jian Lidfba7392016-01-22 16:46:58 -0800291 * @param input input byte array
292 * @return UTF-8 encoded string
Madan Jampanif2f086c2016-01-13 16:15:39 -0800293 */
294 public static String toStringUtf8(byte[] input) {
295 return new String(input, Charsets.UTF_8);
296 }
297
298 /**
Madan Jampani9eb55d12015-08-14 07:47:56 -0700299 * Returns a copy of the input byte array.
300 *
301 * @param original input
302 * @return copy of original
303 */
304 public static byte[] copyOf(byte[] original) {
305 return Arrays.copyOf(original, original.length);
306 }
307
308 /**
Thomas Vachuska6519e6f2015-03-11 02:29:31 -0700309 * Get property as a string value.
310 *
311 * @param properties properties to be looked up
312 * @param propertyName the name of the property to look up
313 * @return value when the propertyName is defined or return null
314 */
315 public static String get(Dictionary<?, ?> properties, String propertyName) {
316 Object v = properties.get(propertyName);
317 String s = (v instanceof String) ? (String) v :
318 v != null ? v.toString() : null;
319 return Strings.isNullOrEmpty(s) ? null : s.trim();
320 }
321
322 /**
Jian Lid9b5f552016-03-11 18:15:31 -0800323 * Get Integer property from the propertyName
324 * Return null if propertyName is not found.
325 *
326 * @param properties properties to be looked up
327 * @param propertyName the name of the property to look up
328 * @return value when the propertyName is defined or return null
329 */
330 public static Integer getIntegerProperty(Dictionary<?, ?> properties,
331 String propertyName) {
332 Integer value;
333 try {
334 String s = get(properties, propertyName);
335 value = Strings.isNullOrEmpty(s) ? null : Integer.valueOf(s);
336 } catch (NumberFormatException | ClassCastException e) {
337 value = null;
338 }
339 return value;
340 }
341
342 /**
343 * Get Integer property from the propertyName
344 * Return default value if propertyName is not found.
345 *
346 * @param properties properties to be looked up
347 * @param propertyName the name of the property to look up
348 * @param defaultValue the default value that to be assigned
349 * @return value when the propertyName is defined or return default value
350 */
351 public static int getIntegerProperty(Dictionary<?, ?> properties,
352 String propertyName,
353 int defaultValue) {
354 try {
355 String s = get(properties, propertyName);
356 return Strings.isNullOrEmpty(s) ? defaultValue : Integer.valueOf(s);
357 } catch (NumberFormatException | ClassCastException e) {
358 return defaultValue;
359 }
360 }
361
362 /**
363 * Check property name is defined and set to true.
364 *
365 * @param properties properties to be looked up
366 * @param propertyName the name of the property to look up
367 * @return value when the propertyName is defined or return null
368 */
369 public static Boolean isPropertyEnabled(Dictionary<?, ?> properties,
370 String propertyName) {
371 Boolean value;
372 try {
373 String s = get(properties, propertyName);
374 value = Strings.isNullOrEmpty(s) ? null : Boolean.valueOf(s);
375 } catch (ClassCastException e) {
376 value = null;
377 }
378 return value;
379 }
380
381 /**
382 * Check property name is defined as set to true.
383 *
384 * @param properties properties to be looked up
385 * @param propertyName the name of the property to look up
386 * @param defaultValue the default value that to be assigned
387 * @return value when the propertyName is defined or return the default value
388 */
389 public static boolean isPropertyEnabled(Dictionary<?, ?> properties,
390 String propertyName,
391 boolean defaultValue) {
392 try {
393 String s = get(properties, propertyName);
394 return Strings.isNullOrEmpty(s) ? defaultValue : Boolean.valueOf(s);
395 } catch (ClassCastException e) {
396 return defaultValue;
397 }
398 }
399
400 /**
tomf110fff2014-09-26 00:38:18 -0700401 * Suspends the current thread for a specified number of millis.
402 *
403 * @param ms number of millis
404 */
405 public static void delay(int ms) {
406 try {
407 Thread.sleep(ms);
408 } catch (InterruptedException e) {
Ray Milkey5c7d4882018-02-05 14:50:39 -0800409 Thread.currentThread().interrupt();
Ray Milkey986a47a2018-01-25 11:38:51 -0800410 throw new IllegalStateException("Interrupted", e);
tomf110fff2014-09-26 00:38:18 -0700411 }
412 }
413
tom53efab52014-10-07 17:43:48 -0700414 /**
sdn94b00152016-08-30 02:12:32 -0700415 * Get Long property from the propertyName
416 * Return null if propertyName is not found.
417 *
418 * @param properties properties to be looked up
419 * @param propertyName the name of the property to look up
420 * @return value when the propertyName is defined or return null
421 */
422 public static Long getLongProperty(Dictionary<?, ?> properties,
423 String propertyName) {
424 Long value;
425 try {
426 String s = get(properties, propertyName);
427 value = Strings.isNullOrEmpty(s) ? null : Long.valueOf(s);
428 } catch (NumberFormatException | ClassCastException e) {
429 value = null;
430 }
431 return value;
432 }
433
434 /**
Georgios Katsikas5e2d40f2018-07-29 18:28:01 +0200435 * Get Float property from the propertyName
436 * Return null if propertyName is not found.
437 *
438 * @param properties properties to be looked up
439 * @param propertyName the name of the property to look up
440 * @return value when the propertyName is defined or return null
441 */
442 public static Float getFloatProperty(Dictionary<?, ?> properties,
443 String propertyName) {
444 Float value;
445 try {
446 String s = get(properties, propertyName);
447 value = Strings.isNullOrEmpty(s) ? null : Float.valueOf(s);
448 } catch (NumberFormatException | ClassCastException e) {
449 value = null;
450 }
451 return value;
452 }
453
454 /**
Madan Jampania29c6772015-08-17 13:17:07 -0700455 * Returns a function that retries execution on failure.
456 * @param base base function
457 * @param exceptionClass type of exception for which to retry
458 * @param maxRetries max number of retries before giving up
459 * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from
460 * the interval (0, maxDelayBetweenRetries]
461 * @return function
Thomas Vachuska87ae1d92015-08-19 17:39:11 -0700462 * @param <U> type of function input
463 * @param <V> type of function output
Madan Jampania29c6772015-08-17 13:17:07 -0700464 */
465 public static <U, V> Function<U, V> retryable(Function<U, V> base,
466 Class<? extends Throwable> exceptionClass,
467 int maxRetries,
468 int maxDelayBetweenRetries) {
469 return new RetryingFunction<>(base, exceptionClass, maxRetries, maxDelayBetweenRetries);
470 }
471
472 /**
473 * Returns a Supplier that retries execution on failure.
474 * @param base base supplier
475 * @param exceptionClass type of exception for which to retry
476 * @param maxRetries max number of retries before giving up
477 * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from
478 * the interval (0, maxDelayBetweenRetries]
479 * @return supplier
Thomas Vachuska87ae1d92015-08-19 17:39:11 -0700480 * @param <V> type of supplied result
Madan Jampania29c6772015-08-17 13:17:07 -0700481 */
482 public static <V> Supplier<V> retryable(Supplier<V> base,
483 Class<? extends Throwable> exceptionClass,
484 int maxRetries,
485 int maxDelayBetweenRetries) {
486 return () -> new RetryingFunction<>(v -> base.get(),
487 exceptionClass,
488 maxRetries,
489 maxDelayBetweenRetries).apply(null);
490 }
491
492 /**
Thomas Vachuskaadba1522015-06-04 15:08:30 -0700493 * Suspends the current thread for a random number of millis between 0 and
494 * the indicated limit.
495 *
496 * @param ms max number of millis
497 */
498 public static void randomDelay(int ms) {
499 try {
500 Thread.sleep(random.nextInt(ms));
501 } catch (InterruptedException e) {
Ray Milkey5c7d4882018-02-05 14:50:39 -0800502 Thread.currentThread().interrupt();
Ray Milkey986a47a2018-01-25 11:38:51 -0800503 throw new IllegalStateException("Interrupted", e);
Thomas Vachuskaadba1522015-06-04 15:08:30 -0700504 }
505 }
506
507 /**
Thomas Vachuskac40d4632015-04-09 16:55:03 -0700508 * Suspends the current thread for a specified number of millis and nanos.
509 *
510 * @param ms number of millis
511 * @param nanos number of nanos
512 */
513 public static void delay(int ms, int nanos) {
514 try {
515 Thread.sleep(ms, nanos);
516 } catch (InterruptedException e) {
Ray Milkey5c7d4882018-02-05 14:50:39 -0800517 Thread.currentThread().interrupt();
Ray Milkey986a47a2018-01-25 11:38:51 -0800518 throw new IllegalStateException("Interrupted", e);
Thomas Vachuskac40d4632015-04-09 16:55:03 -0700519 }
520 }
521
522 /**
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800523 * Purges the specified directory path.&nbsp;Use with great caution since
524 * no attempt is made to check for symbolic links, which could result in
525 * deletion of unintended files.
526 *
527 * @param path directory to be removed
528 * @throws java.io.IOException if unable to remove contents
529 */
530 public static void removeDirectory(String path) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800531 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700532 File dir = new File(path);
533 if (dir.exists() && dir.isDirectory()) {
534 walkFileTree(Paths.get(path), visitor);
535 if (visitor.exception != null) {
536 throw visitor.exception;
537 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800538 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800539 }
540
541 /**
542 * Purges the specified directory path.&nbsp;Use with great caution since
543 * no attempt is made to check for symbolic links, which could result in
544 * deletion of unintended files.
545 *
546 * @param dir directory to be removed
547 * @throws java.io.IOException if unable to remove contents
548 */
549 public static void removeDirectory(File dir) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800550 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700551 if (dir.exists() && dir.isDirectory()) {
552 walkFileTree(Paths.get(dir.getAbsolutePath()), visitor);
553 if (visitor.exception != null) {
554 throw visitor.exception;
555 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800556 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800557 }
558
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800559 // Auxiliary path visitor for recursive directory structure removal.
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800560 private static class DirectoryDeleter extends SimpleFileVisitor<Path> {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800561
562 private IOException exception;
563
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800564 @Override
565 public FileVisitResult visitFile(Path file, BasicFileAttributes attributes)
566 throws IOException {
567 if (attributes.isRegularFile()) {
568 delete(file);
569 }
570 return FileVisitResult.CONTINUE;
571 }
572
573 @Override
574 public FileVisitResult postVisitDirectory(Path directory, IOException ioe)
575 throws IOException {
576 delete(directory);
577 return FileVisitResult.CONTINUE;
578 }
579
580 @Override
581 public FileVisitResult visitFileFailed(Path file, IOException ioe)
582 throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800583 this.exception = ioe;
584 return FileVisitResult.TERMINATE;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800585 }
586 }
587
Madan Jampani30a57f82015-03-02 12:19:41 -0800588 /**
589 * Returns a human friendly time ago string for a specified system time.
Thomas Vachuska480adad2015-03-06 10:27:09 -0800590 *
Madan Jampani30a57f82015-03-02 12:19:41 -0800591 * @param unixTime system time in millis
592 * @return human friendly time ago
593 */
594 public static String timeAgo(long unixTime) {
595 long deltaMillis = System.currentTimeMillis() - unixTime;
596 long secondsSince = (long) (deltaMillis / 1000.0);
597 long minsSince = (long) (deltaMillis / (1000.0 * 60));
598 long hoursSince = (long) (deltaMillis / (1000.0 * 60 * 60));
599 long daysSince = (long) (deltaMillis / (1000.0 * 60 * 60 * 24));
600 if (daysSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800601 return String.format("%dd%dh ago", daysSince, hoursSince - daysSince * 24);
Madan Jampani30a57f82015-03-02 12:19:41 -0800602 } else if (hoursSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800603 return String.format("%dh%dm ago", hoursSince, minsSince - hoursSince * 60);
Madan Jampani30a57f82015-03-02 12:19:41 -0800604 } else if (minsSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800605 return String.format("%dm%ds ago", minsSince, secondsSince - minsSince * 60);
Madan Jampani30a57f82015-03-02 12:19:41 -0800606 } else if (secondsSince > 0) {
607 return String.format("%ds ago", secondsSince);
608 } else {
609 return "just now";
610 }
611 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800612
613 /**
614 * Copies the specified directory path.&nbsp;Use with great caution since
615 * no attempt is made to check for symbolic links, which could result in
616 * copy of unintended files.
617 *
618 * @param src directory to be copied
619 * @param dst destination directory to be removed
620 * @throws java.io.IOException if unable to remove contents
621 */
622 public static void copyDirectory(String src, String dst) throws IOException {
623 walkFileTree(Paths.get(src), new DirectoryCopier(src, dst));
624 }
625
626 /**
627 * Copies the specified directory path.&nbsp;Use with great caution since
628 * no attempt is made to check for symbolic links, which could result in
629 * copy of unintended files.
630 *
631 * @param src directory to be copied
632 * @param dst destination directory to be removed
633 * @throws java.io.IOException if unable to remove contents
634 */
635 public static void copyDirectory(File src, File dst) throws IOException {
636 walkFileTree(Paths.get(src.getAbsolutePath()),
637 new DirectoryCopier(src.getAbsolutePath(),
638 dst.getAbsolutePath()));
639 }
640
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700641 /**
642 * Returns the future value when complete or if future
643 * completes exceptionally returns the defaultValue.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700644 *
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700645 * @param future future
646 * @param defaultValue default value
647 * @param <T> future value type
648 * @return future value when complete or if future
649 * completes exceptionally returns the defaultValue.
650 */
651 public static <T> T futureGetOrElse(Future<T> future, T defaultValue) {
652 try {
653 return future.get();
654 } catch (InterruptedException e) {
655 Thread.currentThread().interrupt();
656 return defaultValue;
657 } catch (ExecutionException e) {
658 return defaultValue;
659 }
660 }
661
662 /**
663 * Returns the future value when complete or if future
664 * completes exceptionally returns the defaultValue.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700665 *
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700666 * @param future future
667 * @param timeout time to wait for successful completion
668 * @param timeUnit time unit
669 * @param defaultValue default value
670 * @param <T> future value type
671 * @return future value when complete or if future
672 * completes exceptionally returns the defaultValue.
673 */
674 public static <T> T futureGetOrElse(Future<T> future,
675 long timeout,
676 TimeUnit timeUnit,
677 T defaultValue) {
678 try {
679 return future.get(timeout, timeUnit);
680 } catch (InterruptedException e) {
681 Thread.currentThread().interrupt();
682 return defaultValue;
683 } catch (ExecutionException | TimeoutException e) {
684 return defaultValue;
685 }
686 }
687
Madan Jampani27b69c62015-05-15 15:49:02 -0700688 /**
689 * Returns a future that is completed exceptionally.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700690 *
Madan Jampani27b69c62015-05-15 15:49:02 -0700691 * @param t exception
692 * @param <T> future value type
693 * @return future
694 */
695 public static <T> CompletableFuture<T> exceptionalFuture(Throwable t) {
696 CompletableFuture<T> future = new CompletableFuture<>();
697 future.completeExceptionally(t);
698 return future;
699 }
700
701 /**
Jordan Halterman046faeb2017-05-01 15:10:13 -0700702 * Returns a future that's completed using the given {@code orderedExecutor} if the future is not blocked or the
703 * given {@code threadPoolExecutor} if the future is blocked.
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700704 * <p>
Jordan Halterman046faeb2017-05-01 15:10:13 -0700705 * This method allows futures to maintain single-thread semantics via the provided {@code orderedExecutor} while
706 * ensuring user code can block without blocking completion of futures. When the returned future or any of its
707 * descendants is blocked on a {@link CompletableFuture#get()} or {@link CompletableFuture#join()} call, completion
708 * of the returned future will be done using the provided {@code threadPoolExecutor}.
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700709 *
710 * @param future the future to convert into an asynchronous future
Jordan Halterman046faeb2017-05-01 15:10:13 -0700711 * @param orderedExecutor the ordered executor with which to attempt to complete the future
712 * @param threadPoolExecutor the backup executor with which to complete blocked futures
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700713 * @param <T> future value type
714 * @return a new completable future to be completed using the provided {@code executor} once the provided
715 * {@code future} is complete
716 */
Jordan Halterman046faeb2017-05-01 15:10:13 -0700717 public static <T> CompletableFuture<T> orderedFuture(
718 CompletableFuture<T> future,
719 Executor orderedExecutor,
720 Executor threadPoolExecutor) {
Jordan Haltermane265d372017-05-17 22:40:47 -0700721 if (future.isDone()) {
722 return future;
723 }
724
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700725 BlockingAwareFuture<T> newFuture = new BlockingAwareFuture<>();
Jordan Halterman046faeb2017-05-01 15:10:13 -0700726 future.whenComplete((result, error) -> {
727 Runnable completer = () -> {
728 if (future.isCompletedExceptionally()) {
729 newFuture.completeExceptionally(error);
730 } else {
731 newFuture.complete(result);
732 }
733 };
734
735 if (newFuture.isBlocked()) {
736 threadPoolExecutor.execute(completer);
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700737 } else {
Jordan Halterman046faeb2017-05-01 15:10:13 -0700738 orderedExecutor.execute(completer);
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700739 }
Jordan Halterman046faeb2017-05-01 15:10:13 -0700740 });
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700741 return newFuture;
742 }
743
744 /**
Sho SHIMIZU85803e22016-01-13 21:53:43 -0800745 * Returns a new CompletableFuture completed with a list of computed values
746 * when all of the given CompletableFuture complete.
747 *
748 * @param futures the CompletableFutures
749 * @param <T> value type of CompletableFuture
750 * @return a new CompletableFuture that is completed when all of the given CompletableFutures complete
751 */
752 public static <T> CompletableFuture<List<T>> allOf(List<CompletableFuture<T>> futures) {
753 return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]))
754 .thenApply(v -> futures.stream()
755 .map(CompletableFuture::join)
756 .collect(Collectors.toList())
757 );
758 }
759
760 /**
Madan Jampani307a21e2016-09-01 15:49:47 -0700761 * Returns a new CompletableFuture completed by reducing a list of computed values
762 * when all of the given CompletableFuture complete.
763 *
764 * @param futures the CompletableFutures
765 * @param reducer reducer for computing the result
766 * @param emptyValue zero value to be returned if the input future list is empty
767 * @param <T> value type of CompletableFuture
768 * @return a new CompletableFuture that is completed when all of the given CompletableFutures complete
769 */
770 public static <T> CompletableFuture<T> allOf(List<CompletableFuture<T>> futures,
771 BinaryOperator<T> reducer,
772 T emptyValue) {
773 return Tools.allOf(futures)
774 .thenApply(resultList -> resultList.stream().reduce(reducer).orElse(emptyValue));
775 }
776
777 /**
Jordan Halterman281dbf32018-06-15 17:46:28 -0700778 * Returns a new CompletableFuture completed with the first result from a list of futures. If no future
779 * is completed successfully, the returned future will be completed with the first exception.
780 *
781 * @param futures the input futures
782 * @param <T> future result type
783 * @return a new CompletableFuture
784 */
785 public static <T> CompletableFuture<T> firstOf(List<CompletableFuture<T>> futures) {
786 CompletableFuture<T> resultFuture = new CompletableFuture<>();
787 CompletableFuture.allOf(futures.stream()
788 .map(future -> future.thenAccept(r -> resultFuture.complete(r)))
789 .toArray(CompletableFuture[]::new))
790 .whenComplete((r, e) -> {
791 if (!resultFuture.isDone()) {
792 if (e != null) {
793 resultFuture.completeExceptionally(e);
794 } else {
795 resultFuture.complete(null);
796 }
797 }
798 });
799 return resultFuture;
800 }
801
802 /**
Madan Jampani307a21e2016-09-01 15:49:47 -0700803 * Returns a new CompletableFuture completed by with the first positive result from a list of
804 * input CompletableFutures.
805 *
806 * @param futures the input list of CompletableFutures
807 * @param positiveResultMatcher matcher to identify a positive result
808 * @param negativeResult value to complete with if none of the futures complete with a positive result
809 * @param <T> value type of CompletableFuture
810 * @return a new CompletableFuture
811 */
812 public static <T> CompletableFuture<T> firstOf(List<CompletableFuture<T>> futures,
813 Match<T> positiveResultMatcher,
814 T negativeResult) {
815 CompletableFuture<T> responseFuture = new CompletableFuture<>();
816 Tools.allOf(Lists.transform(futures, future -> future.thenAccept(r -> {
817 if (positiveResultMatcher.matches(r)) {
818 responseFuture.complete(r);
819 }
820 }))).whenComplete((r, e) -> {
821 if (!responseFuture.isDone()) {
822 if (e != null) {
823 responseFuture.completeExceptionally(e);
824 } else {
825 responseFuture.complete(negativeResult);
826 }
827 }
828 });
829 return responseFuture;
830 }
831
832 /**
Madan Jampani27b69c62015-05-15 15:49:02 -0700833 * Returns the contents of {@code ByteBuffer} as byte array.
834 * <p>
835 * WARNING: There is a performance cost due to array copy
836 * when using this method.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700837 *
Madan Jampani27b69c62015-05-15 15:49:02 -0700838 * @param buffer byte buffer
839 * @return byte array containing the byte buffer contents
840 */
841 public static byte[] byteBuffertoArray(ByteBuffer buffer) {
842 int length = buffer.remaining();
843 if (buffer.hasArray()) {
844 int offset = buffer.arrayOffset() + buffer.position();
845 return Arrays.copyOfRange(buffer.array(), offset, offset + length);
846 }
847 byte[] bytes = new byte[length];
848 buffer.duplicate().get(bytes);
849 return bytes;
850 }
851
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700852 /**
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700853 * Converts an iterable to a stream.
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700854 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700855 * @param it iterable to convert
856 * @param <T> type if item
857 * @return iterable as a stream
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700858 */
859 public static <T> Stream<T> stream(Iterable<T> it) {
860 return StreamSupport.stream(it.spliterator(), false);
861 }
862
Sho SHIMIZUb5638b82016-02-11 14:55:05 -0800863 /**
864 * Converts an optional to a stream.
865 *
866 * @param optional optional to convert
867 * @param <T> type of enclosed value
868 * @return optional as a stream
869 */
Sho SHIMIZU6ac20982016-05-04 09:50:54 -0700870 public static <T> Stream<T> stream(Optional<? extends T> optional) {
HIGUCHI Yuta0bc256f2016-05-06 15:28:26 -0700871 return optional.map(x -> Stream.<T>of(x)).orElse(Stream.empty());
Sho SHIMIZUb5638b82016-02-11 14:55:05 -0800872 }
873
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800874 // Auxiliary path visitor for recursive directory structure copying.
875 private static class DirectoryCopier extends SimpleFileVisitor<Path> {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800876 private Path src;
877 private Path dst;
878 private StandardCopyOption copyOption = StandardCopyOption.REPLACE_EXISTING;
879
880 DirectoryCopier(String src, String dst) {
881 this.src = Paths.get(src);
882 this.dst = Paths.get(dst);
883 }
884
885 @Override
886 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
887 Path targetPath = dst.resolve(src.relativize(dir));
888 if (!Files.exists(targetPath)) {
889 Files.createDirectory(targetPath);
890 }
891 return FileVisitResult.CONTINUE;
892 }
893
894 @Override
895 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
896 Files.copy(file, dst.resolve(src.relativize(file)), copyOption);
897 return FileVisitResult.CONTINUE;
898 }
899 }
900
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700901 /**
902 * Creates OffsetDateTime instance from epoch milliseconds,
903 * using system default time zone.
904 *
905 * @param epochMillis to convert
906 * @return OffsetDateTime
907 */
908 public static OffsetDateTime defaultOffsetDataTime(long epochMillis) {
909 return OffsetDateTime.ofInstant(Instant.ofEpochMilli(epochMillis),
910 ZoneId.systemDefault());
911 }
912
Yuta HIGUCHI47d96092017-11-17 14:05:26 -0800913 /**
914 * Returns smaller of the two Comparable values.
915 *
916 * @param l an argument
917 * @param r another argument
918 * @return the smaller of {@code l} or {@code r}
919 * @param <C> Comparable type
920 * @throws NullPointerException if any of the arguments were null.
921 */
922 public static <C extends Comparable<? super C>> C min(C l, C r) {
923 checkNotNull(l, "l cannot be null");
924 checkNotNull(r, "r cannot be null");
925 return l.compareTo(r) <= 0 ? l : r;
926 }
927
928 /**
929 * Returns larger of the two Comparable values.
930 *
931 * @param l an argument
932 * @param r another argument
933 * @return the larger of {@code l} or {@code r}
934 * @param <C> Comparable type
935 * @throws NullPointerException if any of the arguments were null.
936 */
937 public static <C extends Comparable<? super C>> C max(C l, C r) {
938 checkNotNull(l, "l cannot be null");
939 checkNotNull(r, "r cannot be null");
940 return l.compareTo(r) >= 0 ? l : r;
941 }
Charles Chan58a33da2018-04-04 17:26:46 -0700942
943 /**
944 * Log level for the customized logger.
945 */
946 public enum LogLevel {
947 TRACE, DEBUG, INFO, WARN, ERROR
948 }
949
950 /**
951 * Wrapper function that enables logger invocation with log level as a parameter.
952 *
953 * @param logger logger
954 * @param level log level
955 * @param format format string
956 * @param args objects
957 */
958 public static void log(Logger logger, LogLevel level, String format, Object... args) {
959 switch (level) {
960 case TRACE:
961 logger.trace(format, args);
962 break;
963 case DEBUG:
964 logger.debug(format, args);
965 break;
966 case INFO:
967 logger.info(format, args);
968 break;
969 case WARN:
970 logger.warn(format, args);
971 break;
972 case ERROR:
973 logger.error(format, args);
974 break;
975 default:
976 log.error("Unknown log level {}", level);
977 break;
978 }
979 }
tom5f38b3a2014-08-27 23:50:54 -0700980}