blob: 7725acec2608a5834a94a53818db3fd4273728db [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 Milkeycde7e472018-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 Milkeycde7e472018-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;
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -070038import java.time.Instant;
39import java.time.OffsetDateTime;
40import java.time.ZoneId;
Madan Jampani27b69c62015-05-15 15:49:02 -070041import java.util.Arrays;
Brian O'Connore2eac102015-02-12 18:30:22 -080042import java.util.Collection;
Thomas Vachuska6519e6f2015-03-11 02:29:31 -070043import java.util.Dictionary;
tom53efab52014-10-07 17:43:48 -070044import java.util.List;
Sho SHIMIZUb5638b82016-02-11 14:55:05 -080045import java.util.Optional;
Thomas Vachuskaadba1522015-06-04 15:08:30 -070046import java.util.Random;
Ray Milkey36992c82015-11-17 13:31:15 -080047import java.util.Set;
Madan Jampani27b69c62015-05-15 15:49:02 -070048import java.util.concurrent.CompletableFuture;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070049import java.util.concurrent.ExecutionException;
Jordan Halterman9bdc24f2017-04-19 23:45:12 -070050import java.util.concurrent.Executor;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070051import java.util.concurrent.Future;
tom5f38b3a2014-08-27 23:50:54 -070052import java.util.concurrent.ThreadFactory;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070053import java.util.concurrent.TimeUnit;
54import java.util.concurrent.TimeoutException;
Madan Jampani307a21e2016-09-01 15:49:47 -070055import java.util.function.BinaryOperator;
Madan Jampania29c6772015-08-17 13:17:07 -070056import java.util.function.Function;
57import java.util.function.Supplier;
Sho SHIMIZU85803e22016-01-13 21:53:43 -080058import java.util.stream.Collectors;
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -070059import java.util.stream.Stream;
60import java.util.stream.StreamSupport;
tom5f38b3a2014-08-27 23:50:54 -070061
Jonathan Hartcc962d82016-08-09 16:52:22 -070062import static java.nio.file.Files.delete;
63import static java.nio.file.Files.walkFileTree;
64import static org.onlab.util.GroupedThreadFactory.groupedThreadFactory;
65import static org.slf4j.LoggerFactory.getLogger;
Ray Milkey705d9bc2014-11-18 08:19:00 -080066
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080067/**
68 * Miscellaneous utility methods.
69 */
tom5f38b3a2014-08-27 23:50:54 -070070public abstract class Tools {
71
72 private Tools() {
73 }
74
Thomas Vachuska02aeb032015-01-06 22:36:30 -080075 private static final Logger log = getLogger(Tools.class);
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080076
Thomas Vachuskaadba1522015-06-04 15:08:30 -070077 private static Random random = new Random();
78
Ray Milkeycde7e472018-04-02 15:33:07 -070079 private static final String INPUT_JSON_CANNOT_BE_NULL = "Input JSON cannot be null";
80
tom5f38b3a2014-08-27 23:50:54 -070081 /**
82 * Returns a thread factory that produces threads named according to the
83 * supplied name pattern.
84 *
85 * @param pattern name pattern
86 * @return thread factory
87 */
88 public static ThreadFactory namedThreads(String pattern) {
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080089 return new ThreadFactoryBuilder()
90 .setNameFormat(pattern)
Thomas Vachuska480adad2015-03-06 10:27:09 -080091 .setUncaughtExceptionHandler((t, e) -> log.error("Uncaught exception on " + t.getName(), e))
92 .build();
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080093 }
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080094
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080095 /**
96 * Returns a thread factory that produces threads named according to the
97 * supplied name pattern and from the specified thread-group. The thread
98 * group name is expected to be specified in slash-delimited format, e.g.
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080099 * {@code onos/intent}. The thread names will be produced by converting
100 * the thread group name into dash-delimited format and pre-pended to the
101 * specified pattern.
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -0800102 *
103 * @param groupName group name in slash-delimited format to indicate hierarchy
104 * @param pattern name pattern
105 * @return thread factory
106 */
107 public static ThreadFactory groupedThreads(String groupName, String pattern) {
Jian Li03e9fb02016-03-01 17:13:54 -0800108 return groupedThreads(groupName, pattern, log);
109 }
110
111 /**
112 * Returns a thread factory that produces threads named according to the
113 * supplied name pattern and from the specified thread-group. The thread
114 * group name is expected to be specified in slash-delimited format, e.g.
115 * {@code onos/intent}. The thread names will be produced by converting
116 * the thread group name into dash-delimited format and pre-pended to the
117 * specified pattern. If a logger is specified, it will use the logger to
118 * print out the exception if it has any.
119 *
120 * @param groupName group name in slash-delimited format to indicate hierarchy
121 * @param pattern name pattern
122 * @param logger logger
123 * @return thread factory
124 */
125 public static ThreadFactory groupedThreads(String groupName, String pattern, Logger logger) {
126 if (logger == null) {
127 return groupedThreads(groupName, pattern);
128 }
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -0800129 return new ThreadFactoryBuilder()
130 .setThreadFactory(groupedThreadFactory(groupName))
Thomas Vachuskac13b90a2015-02-18 18:19:55 -0800131 .setNameFormat(groupName.replace(GroupedThreadFactory.DELIMITER, "-") + "-" + pattern)
Jian Li03e9fb02016-03-01 17:13:54 -0800132 .setUncaughtExceptionHandler((t, e) -> logger.error("Uncaught exception on " + t.getName(), e))
Thomas Vachuska480adad2015-03-06 10:27:09 -0800133 .build();
tom5f38b3a2014-08-27 23:50:54 -0700134 }
135
tom782a7cf2014-09-11 23:58:38 -0700136 /**
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800137 * Returns a thread factory that produces threads with MIN_PRIORITY.
138 *
139 * @param factory backing ThreadFactory
140 * @return thread factory
141 */
142 public static ThreadFactory minPriority(ThreadFactory factory) {
143 return new ThreadFactoryBuilder()
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800144 .setThreadFactory(factory)
145 .setPriority(Thread.MIN_PRIORITY)
146 .build();
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800147 }
148
149 /**
Yuta HIGUCHIa2a11cd2016-12-19 14:19:11 -0800150 * Returns a thread factory that produces threads with MAX_PRIORITY.
151 *
152 * @param factory backing ThreadFactory
153 * @return thread factory
154 */
155 public static ThreadFactory maxPriority(ThreadFactory factory) {
156 return new ThreadFactoryBuilder()
157 .setThreadFactory(factory)
158 .setPriority(Thread.MAX_PRIORITY)
159 .build();
160 }
161
162 /**
Brian O'Connore2eac102015-02-12 18:30:22 -0800163 * Returns true if the collection is null or is empty.
164 *
165 * @param collection collection to test
166 * @return true if null or empty; false otherwise
167 */
168 public static boolean isNullOrEmpty(Collection collection) {
169 return collection == null || collection.isEmpty();
170 }
171
172 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700173 * Returns the specified item if that item is not null; otherwise throws
Thomas Vachuskaca88bb72015-04-08 19:38:02 -0700174 * not found exception.
175 *
176 * @param item item to check
177 * @param message not found message
178 * @param <T> item type
179 * @return item if not null
180 * @throws org.onlab.util.ItemNotFoundException if item is null
181 */
182 public static <T> T nullIsNotFound(T item, String message) {
183 if (item == null) {
184 throw new ItemNotFoundException(message);
185 }
186 return item;
187 }
188
189 /**
Ray Milkey36992c82015-11-17 13:31:15 -0800190 * Returns the specified set if the set is not null and not empty;
191 * otherwise throws a not found exception.
192 *
193 * @param item set to check
194 * @param message not found message
195 * @param <T> Set item type
196 * @return item if not null and not empty
197 * @throws org.onlab.util.ItemNotFoundException if set is null or empty
198 */
199 public static <T> Set<T> emptyIsNotFound(Set<T> item, String message) {
200 if (item == null || item.isEmpty()) {
201 throw new ItemNotFoundException(message);
202 }
203 return item;
204 }
205
206 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700207 * Returns the specified item if that item is not null; otherwise throws
208 * bad argument exception.
209 *
210 * @param item item to check
211 * @param message not found message
212 * @param <T> item type
213 * @return item if not null
214 * @throws IllegalArgumentException if item is null
215 */
216 public static <T> T nullIsIllegal(T item, String message) {
217 if (item == null) {
218 throw new IllegalArgumentException(message);
219 }
220 return item;
221 }
222
223 /**
Ray Milkeycde7e472018-04-02 15:33:07 -0700224 * Utility to convert a mapper and an input stream into a JSON tree,
225 * and be tolerant of a null tree being returned.
226 *
227 * @param mapper JSON object mapper
228 * @param stream IO stream containing the JSON
229 * @return object node for the given
230 * @throws IOException if JSON parsing fails
231 */
232 public static ObjectNode readTreeFromStream(ObjectMapper mapper, InputStream stream) throws IOException {
233 return nullIsIllegal((ObjectNode) mapper.readTree(stream), INPUT_JSON_CANNOT_BE_NULL);
234 }
235
236 /**
tom782a7cf2014-09-11 23:58:38 -0700237 * Converts a string from hex to long.
238 *
239 * @param string hex number in string form; sans 0x
240 * @return long value
241 */
242 public static long fromHex(String string) {
243 return UnsignedLongs.parseUnsignedLong(string, 16);
244 }
245
246 /**
247 * Converts a long value to hex string; 16 wide and sans 0x.
248 *
249 * @param value long value
250 * @return hex string
251 */
252 public static String toHex(long value) {
253 return Strings.padStart(UnsignedLongs.toString(value, 16), 16, '0');
254 }
255
256 /**
257 * Converts a long value to hex string; 16 wide and sans 0x.
258 *
259 * @param value long value
260 * @param width string width; zero padded
261 * @return hex string
262 */
263 public static String toHex(long value, int width) {
264 return Strings.padStart(UnsignedLongs.toString(value, 16), width, '0');
265 }
tomf110fff2014-09-26 00:38:18 -0700266
267 /**
Jonathan Hartcc962d82016-08-09 16:52:22 -0700268 * Returns a string encoding in hex of the given long value with prefix
269 * '0x'.
270 *
271 * @param value long value to encode as hex string
272 * @return hex string
273 */
274 public static String toHexWithPrefix(long value) {
275 return "0x" + Long.toHexString(value);
276 }
277
278 /**
Madan Jampanif2f086c2016-01-13 16:15:39 -0800279 * Returns the UTF-8 encoded byte[] representation of a String.
Jian Lidfba7392016-01-22 16:46:58 -0800280 * @param input input string
281 * @return UTF-8 encoded byte array
Madan Jampanif2f086c2016-01-13 16:15:39 -0800282 */
283 public static byte[] getBytesUtf8(String input) {
284 return input.getBytes(Charsets.UTF_8);
285 }
286
287 /**
288 * Returns the String representation of UTF-8 encoded byte[].
Jian Lidfba7392016-01-22 16:46:58 -0800289 * @param input input byte array
290 * @return UTF-8 encoded string
Madan Jampanif2f086c2016-01-13 16:15:39 -0800291 */
292 public static String toStringUtf8(byte[] input) {
293 return new String(input, Charsets.UTF_8);
294 }
295
296 /**
Madan Jampani9eb55d12015-08-14 07:47:56 -0700297 * Returns a copy of the input byte array.
298 *
299 * @param original input
300 * @return copy of original
301 */
302 public static byte[] copyOf(byte[] original) {
303 return Arrays.copyOf(original, original.length);
304 }
305
306 /**
Thomas Vachuska6519e6f2015-03-11 02:29:31 -0700307 * Get property as a string value.
308 *
309 * @param properties properties to be looked up
310 * @param propertyName the name of the property to look up
311 * @return value when the propertyName is defined or return null
312 */
313 public static String get(Dictionary<?, ?> properties, String propertyName) {
314 Object v = properties.get(propertyName);
315 String s = (v instanceof String) ? (String) v :
316 v != null ? v.toString() : null;
317 return Strings.isNullOrEmpty(s) ? null : s.trim();
318 }
319
320 /**
Jian Lid9b5f552016-03-11 18:15:31 -0800321 * Get Integer property from the propertyName
322 * Return null if propertyName is not found.
323 *
324 * @param properties properties to be looked up
325 * @param propertyName the name of the property to look up
326 * @return value when the propertyName is defined or return null
327 */
328 public static Integer getIntegerProperty(Dictionary<?, ?> properties,
329 String propertyName) {
330 Integer value;
331 try {
332 String s = get(properties, propertyName);
333 value = Strings.isNullOrEmpty(s) ? null : Integer.valueOf(s);
334 } catch (NumberFormatException | ClassCastException e) {
335 value = null;
336 }
337 return value;
338 }
339
340 /**
341 * Get Integer property from the propertyName
342 * Return default value if propertyName is not found.
343 *
344 * @param properties properties to be looked up
345 * @param propertyName the name of the property to look up
346 * @param defaultValue the default value that to be assigned
347 * @return value when the propertyName is defined or return default value
348 */
349 public static int getIntegerProperty(Dictionary<?, ?> properties,
350 String propertyName,
351 int defaultValue) {
352 try {
353 String s = get(properties, propertyName);
354 return Strings.isNullOrEmpty(s) ? defaultValue : Integer.valueOf(s);
355 } catch (NumberFormatException | ClassCastException e) {
356 return defaultValue;
357 }
358 }
359
360 /**
361 * Check property name is defined and set to true.
362 *
363 * @param properties properties to be looked up
364 * @param propertyName the name of the property to look up
365 * @return value when the propertyName is defined or return null
366 */
367 public static Boolean isPropertyEnabled(Dictionary<?, ?> properties,
368 String propertyName) {
369 Boolean value;
370 try {
371 String s = get(properties, propertyName);
372 value = Strings.isNullOrEmpty(s) ? null : Boolean.valueOf(s);
373 } catch (ClassCastException e) {
374 value = null;
375 }
376 return value;
377 }
378
379 /**
380 * Check property name is defined as set to true.
381 *
382 * @param properties properties to be looked up
383 * @param propertyName the name of the property to look up
384 * @param defaultValue the default value that to be assigned
385 * @return value when the propertyName is defined or return the default value
386 */
387 public static boolean isPropertyEnabled(Dictionary<?, ?> properties,
388 String propertyName,
389 boolean defaultValue) {
390 try {
391 String s = get(properties, propertyName);
392 return Strings.isNullOrEmpty(s) ? defaultValue : Boolean.valueOf(s);
393 } catch (ClassCastException e) {
394 return defaultValue;
395 }
396 }
397
398 /**
tomf110fff2014-09-26 00:38:18 -0700399 * Suspends the current thread for a specified number of millis.
400 *
401 * @param ms number of millis
402 */
403 public static void delay(int ms) {
404 try {
405 Thread.sleep(ms);
406 } catch (InterruptedException e) {
407 throw new RuntimeException("Interrupted", e);
408 }
409 }
410
tom53efab52014-10-07 17:43:48 -0700411 /**
sdn94b00152016-08-30 02:12:32 -0700412 * Get Long property from the propertyName
413 * Return null if propertyName is not found.
414 *
415 * @param properties properties to be looked up
416 * @param propertyName the name of the property to look up
417 * @return value when the propertyName is defined or return null
418 */
419 public static Long getLongProperty(Dictionary<?, ?> properties,
420 String propertyName) {
421 Long value;
422 try {
423 String s = get(properties, propertyName);
424 value = Strings.isNullOrEmpty(s) ? null : Long.valueOf(s);
425 } catch (NumberFormatException | ClassCastException e) {
426 value = null;
427 }
428 return value;
429 }
430
431 /**
Madan Jampania29c6772015-08-17 13:17:07 -0700432 * Returns a function that retries execution on failure.
433 * @param base base function
434 * @param exceptionClass type of exception for which to retry
435 * @param maxRetries max number of retries before giving up
436 * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from
437 * the interval (0, maxDelayBetweenRetries]
438 * @return function
Thomas Vachuska87ae1d92015-08-19 17:39:11 -0700439 * @param <U> type of function input
440 * @param <V> type of function output
Madan Jampania29c6772015-08-17 13:17:07 -0700441 */
442 public static <U, V> Function<U, V> retryable(Function<U, V> base,
443 Class<? extends Throwable> exceptionClass,
444 int maxRetries,
445 int maxDelayBetweenRetries) {
446 return new RetryingFunction<>(base, exceptionClass, maxRetries, maxDelayBetweenRetries);
447 }
448
449 /**
450 * Returns a Supplier that retries execution on failure.
451 * @param base base supplier
452 * @param exceptionClass type of exception for which to retry
453 * @param maxRetries max number of retries before giving up
454 * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from
455 * the interval (0, maxDelayBetweenRetries]
456 * @return supplier
Thomas Vachuska87ae1d92015-08-19 17:39:11 -0700457 * @param <V> type of supplied result
Madan Jampania29c6772015-08-17 13:17:07 -0700458 */
459 public static <V> Supplier<V> retryable(Supplier<V> base,
460 Class<? extends Throwable> exceptionClass,
461 int maxRetries,
462 int maxDelayBetweenRetries) {
463 return () -> new RetryingFunction<>(v -> base.get(),
464 exceptionClass,
465 maxRetries,
466 maxDelayBetweenRetries).apply(null);
467 }
468
469 /**
Thomas Vachuskaadba1522015-06-04 15:08:30 -0700470 * Suspends the current thread for a random number of millis between 0 and
471 * the indicated limit.
472 *
473 * @param ms max number of millis
474 */
475 public static void randomDelay(int ms) {
476 try {
477 Thread.sleep(random.nextInt(ms));
478 } catch (InterruptedException e) {
479 throw new RuntimeException("Interrupted", e);
480 }
481 }
482
483 /**
Thomas Vachuskac40d4632015-04-09 16:55:03 -0700484 * Suspends the current thread for a specified number of millis and nanos.
485 *
486 * @param ms number of millis
487 * @param nanos number of nanos
488 */
489 public static void delay(int ms, int nanos) {
490 try {
491 Thread.sleep(ms, nanos);
492 } catch (InterruptedException e) {
493 throw new RuntimeException("Interrupted", e);
494 }
495 }
496
497 /**
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800498 * Purges the specified directory path.&nbsp;Use with great caution since
499 * no attempt is made to check for symbolic links, which could result in
500 * deletion of unintended files.
501 *
502 * @param path directory to be removed
503 * @throws java.io.IOException if unable to remove contents
504 */
505 public static void removeDirectory(String path) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800506 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700507 File dir = new File(path);
508 if (dir.exists() && dir.isDirectory()) {
509 walkFileTree(Paths.get(path), visitor);
510 if (visitor.exception != null) {
511 throw visitor.exception;
512 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800513 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800514 }
515
516 /**
517 * Purges the specified directory path.&nbsp;Use with great caution since
518 * no attempt is made to check for symbolic links, which could result in
519 * deletion of unintended files.
520 *
521 * @param dir directory to be removed
522 * @throws java.io.IOException if unable to remove contents
523 */
524 public static void removeDirectory(File dir) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800525 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700526 if (dir.exists() && dir.isDirectory()) {
527 walkFileTree(Paths.get(dir.getAbsolutePath()), visitor);
528 if (visitor.exception != null) {
529 throw visitor.exception;
530 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800531 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800532 }
533
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800534 // Auxiliary path visitor for recursive directory structure removal.
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800535 private static class DirectoryDeleter extends SimpleFileVisitor<Path> {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800536
537 private IOException exception;
538
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800539 @Override
540 public FileVisitResult visitFile(Path file, BasicFileAttributes attributes)
541 throws IOException {
542 if (attributes.isRegularFile()) {
543 delete(file);
544 }
545 return FileVisitResult.CONTINUE;
546 }
547
548 @Override
549 public FileVisitResult postVisitDirectory(Path directory, IOException ioe)
550 throws IOException {
551 delete(directory);
552 return FileVisitResult.CONTINUE;
553 }
554
555 @Override
556 public FileVisitResult visitFileFailed(Path file, IOException ioe)
557 throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800558 this.exception = ioe;
559 return FileVisitResult.TERMINATE;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800560 }
561 }
562
Madan Jampani30a57f82015-03-02 12:19:41 -0800563 /**
564 * Returns a human friendly time ago string for a specified system time.
Thomas Vachuska480adad2015-03-06 10:27:09 -0800565 *
Madan Jampani30a57f82015-03-02 12:19:41 -0800566 * @param unixTime system time in millis
567 * @return human friendly time ago
568 */
569 public static String timeAgo(long unixTime) {
570 long deltaMillis = System.currentTimeMillis() - unixTime;
571 long secondsSince = (long) (deltaMillis / 1000.0);
572 long minsSince = (long) (deltaMillis / (1000.0 * 60));
573 long hoursSince = (long) (deltaMillis / (1000.0 * 60 * 60));
574 long daysSince = (long) (deltaMillis / (1000.0 * 60 * 60 * 24));
575 if (daysSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800576 return String.format("%dd%dh ago", daysSince, hoursSince - daysSince * 24);
Madan Jampani30a57f82015-03-02 12:19:41 -0800577 } else if (hoursSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800578 return String.format("%dh%dm ago", hoursSince, minsSince - hoursSince * 60);
Madan Jampani30a57f82015-03-02 12:19:41 -0800579 } else if (minsSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800580 return String.format("%dm%ds ago", minsSince, secondsSince - minsSince * 60);
Madan Jampani30a57f82015-03-02 12:19:41 -0800581 } else if (secondsSince > 0) {
582 return String.format("%ds ago", secondsSince);
583 } else {
584 return "just now";
585 }
586 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800587
588 /**
589 * Copies the specified directory path.&nbsp;Use with great caution since
590 * no attempt is made to check for symbolic links, which could result in
591 * copy of unintended files.
592 *
593 * @param src directory to be copied
594 * @param dst destination directory to be removed
595 * @throws java.io.IOException if unable to remove contents
596 */
597 public static void copyDirectory(String src, String dst) throws IOException {
598 walkFileTree(Paths.get(src), new DirectoryCopier(src, dst));
599 }
600
601 /**
602 * Copies the specified directory path.&nbsp;Use with great caution since
603 * no attempt is made to check for symbolic links, which could result in
604 * copy of unintended files.
605 *
606 * @param src directory to be copied
607 * @param dst destination directory to be removed
608 * @throws java.io.IOException if unable to remove contents
609 */
610 public static void copyDirectory(File src, File dst) throws IOException {
611 walkFileTree(Paths.get(src.getAbsolutePath()),
612 new DirectoryCopier(src.getAbsolutePath(),
613 dst.getAbsolutePath()));
614 }
615
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700616 /**
617 * Returns the future value when complete or if future
618 * completes exceptionally returns the defaultValue.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700619 *
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700620 * @param future future
621 * @param defaultValue default value
622 * @param <T> future value type
623 * @return future value when complete or if future
624 * completes exceptionally returns the defaultValue.
625 */
626 public static <T> T futureGetOrElse(Future<T> future, T defaultValue) {
627 try {
628 return future.get();
629 } catch (InterruptedException e) {
630 Thread.currentThread().interrupt();
631 return defaultValue;
632 } catch (ExecutionException e) {
633 return defaultValue;
634 }
635 }
636
637 /**
638 * Returns the future value when complete or if future
639 * completes exceptionally returns the defaultValue.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700640 *
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700641 * @param future future
642 * @param timeout time to wait for successful completion
643 * @param timeUnit time unit
644 * @param defaultValue default value
645 * @param <T> future value type
646 * @return future value when complete or if future
647 * completes exceptionally returns the defaultValue.
648 */
649 public static <T> T futureGetOrElse(Future<T> future,
650 long timeout,
651 TimeUnit timeUnit,
652 T defaultValue) {
653 try {
654 return future.get(timeout, timeUnit);
655 } catch (InterruptedException e) {
656 Thread.currentThread().interrupt();
657 return defaultValue;
658 } catch (ExecutionException | TimeoutException e) {
659 return defaultValue;
660 }
661 }
662
Madan Jampani27b69c62015-05-15 15:49:02 -0700663 /**
664 * Returns a future that is completed exceptionally.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700665 *
Madan Jampani27b69c62015-05-15 15:49:02 -0700666 * @param t exception
667 * @param <T> future value type
668 * @return future
669 */
670 public static <T> CompletableFuture<T> exceptionalFuture(Throwable t) {
671 CompletableFuture<T> future = new CompletableFuture<>();
672 future.completeExceptionally(t);
673 return future;
674 }
675
676 /**
Jordan Halterman046faeb2017-05-01 15:10:13 -0700677 * Returns a future that's completed using the given {@code orderedExecutor} if the future is not blocked or the
678 * given {@code threadPoolExecutor} if the future is blocked.
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700679 * <p>
Jordan Halterman046faeb2017-05-01 15:10:13 -0700680 * This method allows futures to maintain single-thread semantics via the provided {@code orderedExecutor} while
681 * ensuring user code can block without blocking completion of futures. When the returned future or any of its
682 * descendants is blocked on a {@link CompletableFuture#get()} or {@link CompletableFuture#join()} call, completion
683 * of the returned future will be done using the provided {@code threadPoolExecutor}.
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700684 *
685 * @param future the future to convert into an asynchronous future
Jordan Halterman046faeb2017-05-01 15:10:13 -0700686 * @param orderedExecutor the ordered executor with which to attempt to complete the future
687 * @param threadPoolExecutor the backup executor with which to complete blocked futures
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700688 * @param <T> future value type
689 * @return a new completable future to be completed using the provided {@code executor} once the provided
690 * {@code future} is complete
691 */
Jordan Halterman046faeb2017-05-01 15:10:13 -0700692 public static <T> CompletableFuture<T> orderedFuture(
693 CompletableFuture<T> future,
694 Executor orderedExecutor,
695 Executor threadPoolExecutor) {
Jordan Haltermane265d372017-05-17 22:40:47 -0700696 if (future.isDone()) {
697 return future;
698 }
699
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700700 BlockingAwareFuture<T> newFuture = new BlockingAwareFuture<>();
Jordan Halterman046faeb2017-05-01 15:10:13 -0700701 future.whenComplete((result, error) -> {
702 Runnable completer = () -> {
703 if (future.isCompletedExceptionally()) {
704 newFuture.completeExceptionally(error);
705 } else {
706 newFuture.complete(result);
707 }
708 };
709
710 if (newFuture.isBlocked()) {
711 threadPoolExecutor.execute(completer);
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700712 } else {
Jordan Halterman046faeb2017-05-01 15:10:13 -0700713 orderedExecutor.execute(completer);
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700714 }
Jordan Halterman046faeb2017-05-01 15:10:13 -0700715 });
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700716 return newFuture;
717 }
718
719 /**
Sho SHIMIZU85803e22016-01-13 21:53:43 -0800720 * Returns a new CompletableFuture completed with a list of computed values
721 * when all of the given CompletableFuture complete.
722 *
723 * @param futures the CompletableFutures
724 * @param <T> value type of CompletableFuture
725 * @return a new CompletableFuture that is completed when all of the given CompletableFutures complete
726 */
727 public static <T> CompletableFuture<List<T>> allOf(List<CompletableFuture<T>> futures) {
728 return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]))
729 .thenApply(v -> futures.stream()
730 .map(CompletableFuture::join)
731 .collect(Collectors.toList())
732 );
733 }
734
735 /**
Madan Jampani307a21e2016-09-01 15:49:47 -0700736 * Returns a new CompletableFuture completed by reducing a list of computed values
737 * when all of the given CompletableFuture complete.
738 *
739 * @param futures the CompletableFutures
740 * @param reducer reducer for computing the result
741 * @param emptyValue zero value to be returned if the input future list is empty
742 * @param <T> value type of CompletableFuture
743 * @return a new CompletableFuture that is completed when all of the given CompletableFutures complete
744 */
745 public static <T> CompletableFuture<T> allOf(List<CompletableFuture<T>> futures,
746 BinaryOperator<T> reducer,
747 T emptyValue) {
748 return Tools.allOf(futures)
749 .thenApply(resultList -> resultList.stream().reduce(reducer).orElse(emptyValue));
750 }
751
752 /**
753 * Returns a new CompletableFuture completed by with the first positive result from a list of
754 * input CompletableFutures.
755 *
756 * @param futures the input list of CompletableFutures
757 * @param positiveResultMatcher matcher to identify a positive result
758 * @param negativeResult value to complete with if none of the futures complete with a positive result
759 * @param <T> value type of CompletableFuture
760 * @return a new CompletableFuture
761 */
762 public static <T> CompletableFuture<T> firstOf(List<CompletableFuture<T>> futures,
763 Match<T> positiveResultMatcher,
764 T negativeResult) {
765 CompletableFuture<T> responseFuture = new CompletableFuture<>();
766 Tools.allOf(Lists.transform(futures, future -> future.thenAccept(r -> {
767 if (positiveResultMatcher.matches(r)) {
768 responseFuture.complete(r);
769 }
770 }))).whenComplete((r, e) -> {
771 if (!responseFuture.isDone()) {
772 if (e != null) {
773 responseFuture.completeExceptionally(e);
774 } else {
775 responseFuture.complete(negativeResult);
776 }
777 }
778 });
779 return responseFuture;
780 }
781
782 /**
Madan Jampani27b69c62015-05-15 15:49:02 -0700783 * Returns the contents of {@code ByteBuffer} as byte array.
784 * <p>
785 * WARNING: There is a performance cost due to array copy
786 * when using this method.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700787 *
Madan Jampani27b69c62015-05-15 15:49:02 -0700788 * @param buffer byte buffer
789 * @return byte array containing the byte buffer contents
790 */
791 public static byte[] byteBuffertoArray(ByteBuffer buffer) {
792 int length = buffer.remaining();
793 if (buffer.hasArray()) {
794 int offset = buffer.arrayOffset() + buffer.position();
795 return Arrays.copyOfRange(buffer.array(), offset, offset + length);
796 }
797 byte[] bytes = new byte[length];
798 buffer.duplicate().get(bytes);
799 return bytes;
800 }
801
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700802 /**
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700803 * Converts an iterable to a stream.
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700804 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700805 * @param it iterable to convert
806 * @param <T> type if item
807 * @return iterable as a stream
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700808 */
809 public static <T> Stream<T> stream(Iterable<T> it) {
810 return StreamSupport.stream(it.spliterator(), false);
811 }
812
Sho SHIMIZUb5638b82016-02-11 14:55:05 -0800813 /**
814 * Converts an optional to a stream.
815 *
816 * @param optional optional to convert
817 * @param <T> type of enclosed value
818 * @return optional as a stream
819 */
Sho SHIMIZU6ac20982016-05-04 09:50:54 -0700820 public static <T> Stream<T> stream(Optional<? extends T> optional) {
HIGUCHI Yuta0bc256f2016-05-06 15:28:26 -0700821 return optional.map(x -> Stream.<T>of(x)).orElse(Stream.empty());
Sho SHIMIZUb5638b82016-02-11 14:55:05 -0800822 }
823
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800824 // Auxiliary path visitor for recursive directory structure copying.
825 private static class DirectoryCopier extends SimpleFileVisitor<Path> {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800826 private Path src;
827 private Path dst;
828 private StandardCopyOption copyOption = StandardCopyOption.REPLACE_EXISTING;
829
830 DirectoryCopier(String src, String dst) {
831 this.src = Paths.get(src);
832 this.dst = Paths.get(dst);
833 }
834
835 @Override
836 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
837 Path targetPath = dst.resolve(src.relativize(dir));
838 if (!Files.exists(targetPath)) {
839 Files.createDirectory(targetPath);
840 }
841 return FileVisitResult.CONTINUE;
842 }
843
844 @Override
845 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
846 Files.copy(file, dst.resolve(src.relativize(file)), copyOption);
847 return FileVisitResult.CONTINUE;
848 }
849 }
850
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700851 /**
852 * Creates OffsetDateTime instance from epoch milliseconds,
853 * using system default time zone.
854 *
855 * @param epochMillis to convert
856 * @return OffsetDateTime
857 */
858 public static OffsetDateTime defaultOffsetDataTime(long epochMillis) {
859 return OffsetDateTime.ofInstant(Instant.ofEpochMilli(epochMillis),
860 ZoneId.systemDefault());
861 }
862
Charles Chanb7e37392018-04-04 17:26:46 -0700863 /**
864 * Log level for the customized logger.
865 */
866 public enum LogLevel {
867 TRACE, DEBUG, INFO, WARN, ERROR
868 }
869
870 /**
871 * Wrapper function that enables logger invocation with log level as a parameter.
872 *
873 * @param logger logger
874 * @param level log level
875 * @param format format string
876 * @param args objects
877 */
878 public static void log(Logger logger, LogLevel level, String format, Object... args) {
879 switch (level) {
880 case TRACE:
881 logger.trace(format, args);
882 break;
883 case DEBUG:
884 logger.debug(format, args);
885 break;
886 case INFO:
887 logger.info(format, args);
888 break;
889 case WARN:
890 logger.warn(format, args);
891 break;
892 case ERROR:
893 logger.error(format, args);
894 break;
895 default:
896 log.error("Unknown log level {}", level);
897 break;
898 }
899 }
tom5f38b3a2014-08-27 23:50:54 -0700900}