blob: 057441e30221fba05a65db31e8ca7dd021a90537 [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
Jonathan Hartcc962d82016-08-09 16:52:22 -070018import com.google.common.base.Charsets;
19import com.google.common.base.Strings;
20import com.google.common.collect.Lists;
21import com.google.common.primitives.UnsignedLongs;
22import com.google.common.util.concurrent.ThreadFactoryBuilder;
23import org.slf4j.Logger;
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080024
tom53efab52014-10-07 17:43:48 -070025import java.io.File;
tom53efab52014-10-07 17:43:48 -070026import java.io.IOException;
Madan Jampani27b69c62015-05-15 15:49:02 -070027import java.nio.ByteBuffer;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080028import java.nio.file.FileVisitResult;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080029import java.nio.file.Files;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080030import java.nio.file.Path;
31import java.nio.file.Paths;
32import java.nio.file.SimpleFileVisitor;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080033import java.nio.file.StandardCopyOption;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080034import java.nio.file.attribute.BasicFileAttributes;
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -070035import java.time.Instant;
36import java.time.OffsetDateTime;
37import java.time.ZoneId;
Madan Jampani27b69c62015-05-15 15:49:02 -070038import java.util.Arrays;
Brian O'Connore2eac102015-02-12 18:30:22 -080039import java.util.Collection;
Thomas Vachuska6519e6f2015-03-11 02:29:31 -070040import java.util.Dictionary;
tom53efab52014-10-07 17:43:48 -070041import java.util.List;
Sho SHIMIZUb5638b82016-02-11 14:55:05 -080042import java.util.Optional;
Thomas Vachuskaadba1522015-06-04 15:08:30 -070043import java.util.Random;
Ray Milkey36992c82015-11-17 13:31:15 -080044import java.util.Set;
Madan Jampani27b69c62015-05-15 15:49:02 -070045import java.util.concurrent.CompletableFuture;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070046import java.util.concurrent.ExecutionException;
Jordan Halterman9bdc24f2017-04-19 23:45:12 -070047import java.util.concurrent.Executor;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070048import java.util.concurrent.Future;
tom5f38b3a2014-08-27 23:50:54 -070049import java.util.concurrent.ThreadFactory;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070050import java.util.concurrent.TimeUnit;
51import java.util.concurrent.TimeoutException;
Madan Jampani307a21e2016-09-01 15:49:47 -070052import java.util.function.BinaryOperator;
Madan Jampania29c6772015-08-17 13:17:07 -070053import java.util.function.Function;
54import java.util.function.Supplier;
Sho SHIMIZU85803e22016-01-13 21:53:43 -080055import java.util.stream.Collectors;
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -070056import java.util.stream.Stream;
57import java.util.stream.StreamSupport;
tom5f38b3a2014-08-27 23:50:54 -070058
Jonathan Hartcc962d82016-08-09 16:52:22 -070059import static java.nio.file.Files.delete;
60import static java.nio.file.Files.walkFileTree;
61import static org.onlab.util.GroupedThreadFactory.groupedThreadFactory;
62import static org.slf4j.LoggerFactory.getLogger;
Ray Milkey705d9bc2014-11-18 08:19:00 -080063
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080064/**
65 * Miscellaneous utility methods.
66 */
tom5f38b3a2014-08-27 23:50:54 -070067public abstract class Tools {
68
69 private Tools() {
70 }
71
Thomas Vachuska02aeb032015-01-06 22:36:30 -080072 private static final Logger log = getLogger(Tools.class);
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080073
Thomas Vachuskaadba1522015-06-04 15:08:30 -070074 private static Random random = new Random();
75
tom5f38b3a2014-08-27 23:50:54 -070076 /**
77 * Returns a thread factory that produces threads named according to the
78 * supplied name pattern.
79 *
80 * @param pattern name pattern
81 * @return thread factory
82 */
83 public static ThreadFactory namedThreads(String pattern) {
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080084 return new ThreadFactoryBuilder()
85 .setNameFormat(pattern)
Thomas Vachuska480adad2015-03-06 10:27:09 -080086 .setUncaughtExceptionHandler((t, e) -> log.error("Uncaught exception on " + t.getName(), e))
87 .build();
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080088 }
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080089
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080090 /**
91 * Returns a thread factory that produces threads named according to the
92 * supplied name pattern and from the specified thread-group. The thread
93 * group name is expected to be specified in slash-delimited format, e.g.
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080094 * {@code onos/intent}. The thread names will be produced by converting
95 * the thread group name into dash-delimited format and pre-pended to the
96 * specified pattern.
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080097 *
98 * @param groupName group name in slash-delimited format to indicate hierarchy
99 * @param pattern name pattern
100 * @return thread factory
101 */
102 public static ThreadFactory groupedThreads(String groupName, String pattern) {
Jian Li03e9fb02016-03-01 17:13:54 -0800103 return groupedThreads(groupName, pattern, log);
104 }
105
106 /**
107 * Returns a thread factory that produces threads named according to the
108 * supplied name pattern and from the specified thread-group. The thread
109 * group name is expected to be specified in slash-delimited format, e.g.
110 * {@code onos/intent}. The thread names will be produced by converting
111 * the thread group name into dash-delimited format and pre-pended to the
112 * specified pattern. If a logger is specified, it will use the logger to
113 * print out the exception if it has any.
114 *
115 * @param groupName group name in slash-delimited format to indicate hierarchy
116 * @param pattern name pattern
117 * @param logger logger
118 * @return thread factory
119 */
120 public static ThreadFactory groupedThreads(String groupName, String pattern, Logger logger) {
121 if (logger == null) {
122 return groupedThreads(groupName, pattern);
123 }
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -0800124 return new ThreadFactoryBuilder()
125 .setThreadFactory(groupedThreadFactory(groupName))
Thomas Vachuskac13b90a2015-02-18 18:19:55 -0800126 .setNameFormat(groupName.replace(GroupedThreadFactory.DELIMITER, "-") + "-" + pattern)
Jian Li03e9fb02016-03-01 17:13:54 -0800127 .setUncaughtExceptionHandler((t, e) -> logger.error("Uncaught exception on " + t.getName(), e))
Thomas Vachuska480adad2015-03-06 10:27:09 -0800128 .build();
tom5f38b3a2014-08-27 23:50:54 -0700129 }
130
tom782a7cf2014-09-11 23:58:38 -0700131 /**
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800132 * Returns a thread factory that produces threads with MIN_PRIORITY.
133 *
134 * @param factory backing ThreadFactory
135 * @return thread factory
136 */
137 public static ThreadFactory minPriority(ThreadFactory factory) {
138 return new ThreadFactoryBuilder()
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800139 .setThreadFactory(factory)
140 .setPriority(Thread.MIN_PRIORITY)
141 .build();
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800142 }
143
144 /**
Yuta HIGUCHIa2a11cd2016-12-19 14:19:11 -0800145 * Returns a thread factory that produces threads with MAX_PRIORITY.
146 *
147 * @param factory backing ThreadFactory
148 * @return thread factory
149 */
150 public static ThreadFactory maxPriority(ThreadFactory factory) {
151 return new ThreadFactoryBuilder()
152 .setThreadFactory(factory)
153 .setPriority(Thread.MAX_PRIORITY)
154 .build();
155 }
156
157 /**
Brian O'Connore2eac102015-02-12 18:30:22 -0800158 * Returns true if the collection is null or is empty.
159 *
160 * @param collection collection to test
161 * @return true if null or empty; false otherwise
162 */
163 public static boolean isNullOrEmpty(Collection collection) {
164 return collection == null || collection.isEmpty();
165 }
166
167 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700168 * Returns the specified item if that item is not null; otherwise throws
Thomas Vachuskaca88bb72015-04-08 19:38:02 -0700169 * not found exception.
170 *
171 * @param item item to check
172 * @param message not found message
173 * @param <T> item type
174 * @return item if not null
175 * @throws org.onlab.util.ItemNotFoundException if item is null
176 */
177 public static <T> T nullIsNotFound(T item, String message) {
178 if (item == null) {
179 throw new ItemNotFoundException(message);
180 }
181 return item;
182 }
183
184 /**
Ray Milkey36992c82015-11-17 13:31:15 -0800185 * Returns the specified set if the set is not null and not empty;
186 * otherwise throws a not found exception.
187 *
188 * @param item set to check
189 * @param message not found message
190 * @param <T> Set item type
191 * @return item if not null and not empty
192 * @throws org.onlab.util.ItemNotFoundException if set is null or empty
193 */
194 public static <T> Set<T> emptyIsNotFound(Set<T> item, String message) {
195 if (item == null || item.isEmpty()) {
196 throw new ItemNotFoundException(message);
197 }
198 return item;
199 }
200
201 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700202 * Returns the specified item if that item is not null; otherwise throws
203 * bad argument exception.
204 *
205 * @param item item to check
206 * @param message not found message
207 * @param <T> item type
208 * @return item if not null
209 * @throws IllegalArgumentException if item is null
210 */
211 public static <T> T nullIsIllegal(T item, String message) {
212 if (item == null) {
213 throw new IllegalArgumentException(message);
214 }
215 return item;
216 }
217
218 /**
tom782a7cf2014-09-11 23:58:38 -0700219 * Converts a string from hex to long.
220 *
221 * @param string hex number in string form; sans 0x
222 * @return long value
223 */
224 public static long fromHex(String string) {
225 return UnsignedLongs.parseUnsignedLong(string, 16);
226 }
227
228 /**
229 * Converts a long value to hex string; 16 wide and sans 0x.
230 *
231 * @param value long value
232 * @return hex string
233 */
234 public static String toHex(long value) {
235 return Strings.padStart(UnsignedLongs.toString(value, 16), 16, '0');
236 }
237
238 /**
239 * Converts a long value to hex string; 16 wide and sans 0x.
240 *
241 * @param value long value
242 * @param width string width; zero padded
243 * @return hex string
244 */
245 public static String toHex(long value, int width) {
246 return Strings.padStart(UnsignedLongs.toString(value, 16), width, '0');
247 }
tomf110fff2014-09-26 00:38:18 -0700248
249 /**
Jonathan Hartcc962d82016-08-09 16:52:22 -0700250 * Returns a string encoding in hex of the given long value with prefix
251 * '0x'.
252 *
253 * @param value long value to encode as hex string
254 * @return hex string
255 */
256 public static String toHexWithPrefix(long value) {
257 return "0x" + Long.toHexString(value);
258 }
259
260 /**
Madan Jampanif2f086c2016-01-13 16:15:39 -0800261 * Returns the UTF-8 encoded byte[] representation of a String.
Jian Lidfba7392016-01-22 16:46:58 -0800262 * @param input input string
263 * @return UTF-8 encoded byte array
Madan Jampanif2f086c2016-01-13 16:15:39 -0800264 */
265 public static byte[] getBytesUtf8(String input) {
266 return input.getBytes(Charsets.UTF_8);
267 }
268
269 /**
270 * Returns the String representation of UTF-8 encoded byte[].
Jian Lidfba7392016-01-22 16:46:58 -0800271 * @param input input byte array
272 * @return UTF-8 encoded string
Madan Jampanif2f086c2016-01-13 16:15:39 -0800273 */
274 public static String toStringUtf8(byte[] input) {
275 return new String(input, Charsets.UTF_8);
276 }
277
278 /**
Madan Jampani9eb55d12015-08-14 07:47:56 -0700279 * Returns a copy of the input byte array.
280 *
281 * @param original input
282 * @return copy of original
283 */
284 public static byte[] copyOf(byte[] original) {
285 return Arrays.copyOf(original, original.length);
286 }
287
288 /**
Thomas Vachuska6519e6f2015-03-11 02:29:31 -0700289 * Get property as a string value.
290 *
291 * @param properties properties to be looked up
292 * @param propertyName the name of the property to look up
293 * @return value when the propertyName is defined or return null
294 */
295 public static String get(Dictionary<?, ?> properties, String propertyName) {
296 Object v = properties.get(propertyName);
297 String s = (v instanceof String) ? (String) v :
298 v != null ? v.toString() : null;
299 return Strings.isNullOrEmpty(s) ? null : s.trim();
300 }
301
302 /**
Jian Lid9b5f552016-03-11 18:15:31 -0800303 * Get Integer property from the propertyName
304 * Return null if propertyName is not found.
305 *
306 * @param properties properties to be looked up
307 * @param propertyName the name of the property to look up
308 * @return value when the propertyName is defined or return null
309 */
310 public static Integer getIntegerProperty(Dictionary<?, ?> properties,
311 String propertyName) {
312 Integer value;
313 try {
314 String s = get(properties, propertyName);
315 value = Strings.isNullOrEmpty(s) ? null : Integer.valueOf(s);
316 } catch (NumberFormatException | ClassCastException e) {
317 value = null;
318 }
319 return value;
320 }
321
322 /**
323 * Get Integer property from the propertyName
324 * Return default value 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 * @param defaultValue the default value that to be assigned
329 * @return value when the propertyName is defined or return default value
330 */
331 public static int getIntegerProperty(Dictionary<?, ?> properties,
332 String propertyName,
333 int defaultValue) {
334 try {
335 String s = get(properties, propertyName);
336 return Strings.isNullOrEmpty(s) ? defaultValue : Integer.valueOf(s);
337 } catch (NumberFormatException | ClassCastException e) {
338 return defaultValue;
339 }
340 }
341
342 /**
343 * Check property name is defined and set to true.
344 *
345 * @param properties properties to be looked up
346 * @param propertyName the name of the property to look up
347 * @return value when the propertyName is defined or return null
348 */
349 public static Boolean isPropertyEnabled(Dictionary<?, ?> properties,
350 String propertyName) {
351 Boolean value;
352 try {
353 String s = get(properties, propertyName);
354 value = Strings.isNullOrEmpty(s) ? null : Boolean.valueOf(s);
355 } catch (ClassCastException e) {
356 value = null;
357 }
358 return value;
359 }
360
361 /**
362 * Check property name is defined as set to true.
363 *
364 * @param properties properties to be looked up
365 * @param propertyName the name of the property to look up
366 * @param defaultValue the default value that to be assigned
367 * @return value when the propertyName is defined or return the default value
368 */
369 public static boolean isPropertyEnabled(Dictionary<?, ?> properties,
370 String propertyName,
371 boolean defaultValue) {
372 try {
373 String s = get(properties, propertyName);
374 return Strings.isNullOrEmpty(s) ? defaultValue : Boolean.valueOf(s);
375 } catch (ClassCastException e) {
376 return defaultValue;
377 }
378 }
379
380 /**
tomf110fff2014-09-26 00:38:18 -0700381 * Suspends the current thread for a specified number of millis.
382 *
383 * @param ms number of millis
384 */
385 public static void delay(int ms) {
386 try {
387 Thread.sleep(ms);
388 } catch (InterruptedException e) {
389 throw new RuntimeException("Interrupted", e);
390 }
391 }
392
tom53efab52014-10-07 17:43:48 -0700393 /**
sdn94b00152016-08-30 02:12:32 -0700394 * Get Long property from the propertyName
395 * Return null if propertyName is not found.
396 *
397 * @param properties properties to be looked up
398 * @param propertyName the name of the property to look up
399 * @return value when the propertyName is defined or return null
400 */
401 public static Long getLongProperty(Dictionary<?, ?> properties,
402 String propertyName) {
403 Long value;
404 try {
405 String s = get(properties, propertyName);
406 value = Strings.isNullOrEmpty(s) ? null : Long.valueOf(s);
407 } catch (NumberFormatException | ClassCastException e) {
408 value = null;
409 }
410 return value;
411 }
412
413 /**
Madan Jampania29c6772015-08-17 13:17:07 -0700414 * Returns a function that retries execution on failure.
415 * @param base base function
416 * @param exceptionClass type of exception for which to retry
417 * @param maxRetries max number of retries before giving up
418 * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from
419 * the interval (0, maxDelayBetweenRetries]
420 * @return function
Thomas Vachuska87ae1d92015-08-19 17:39:11 -0700421 * @param <U> type of function input
422 * @param <V> type of function output
Madan Jampania29c6772015-08-17 13:17:07 -0700423 */
424 public static <U, V> Function<U, V> retryable(Function<U, V> base,
425 Class<? extends Throwable> exceptionClass,
426 int maxRetries,
427 int maxDelayBetweenRetries) {
428 return new RetryingFunction<>(base, exceptionClass, maxRetries, maxDelayBetweenRetries);
429 }
430
431 /**
432 * Returns a Supplier that retries execution on failure.
433 * @param base base supplier
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 supplier
Thomas Vachuska87ae1d92015-08-19 17:39:11 -0700439 * @param <V> type of supplied result
Madan Jampania29c6772015-08-17 13:17:07 -0700440 */
441 public static <V> Supplier<V> retryable(Supplier<V> base,
442 Class<? extends Throwable> exceptionClass,
443 int maxRetries,
444 int maxDelayBetweenRetries) {
445 return () -> new RetryingFunction<>(v -> base.get(),
446 exceptionClass,
447 maxRetries,
448 maxDelayBetweenRetries).apply(null);
449 }
450
451 /**
Thomas Vachuskaadba1522015-06-04 15:08:30 -0700452 * Suspends the current thread for a random number of millis between 0 and
453 * the indicated limit.
454 *
455 * @param ms max number of millis
456 */
457 public static void randomDelay(int ms) {
458 try {
459 Thread.sleep(random.nextInt(ms));
460 } catch (InterruptedException e) {
461 throw new RuntimeException("Interrupted", e);
462 }
463 }
464
465 /**
Thomas Vachuskac40d4632015-04-09 16:55:03 -0700466 * Suspends the current thread for a specified number of millis and nanos.
467 *
468 * @param ms number of millis
469 * @param nanos number of nanos
470 */
471 public static void delay(int ms, int nanos) {
472 try {
473 Thread.sleep(ms, nanos);
474 } catch (InterruptedException e) {
475 throw new RuntimeException("Interrupted", e);
476 }
477 }
478
479 /**
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800480 * Purges the specified directory path.&nbsp;Use with great caution since
481 * no attempt is made to check for symbolic links, which could result in
482 * deletion of unintended files.
483 *
484 * @param path directory to be removed
485 * @throws java.io.IOException if unable to remove contents
486 */
487 public static void removeDirectory(String path) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800488 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700489 File dir = new File(path);
490 if (dir.exists() && dir.isDirectory()) {
491 walkFileTree(Paths.get(path), visitor);
492 if (visitor.exception != null) {
493 throw visitor.exception;
494 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800495 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800496 }
497
498 /**
499 * Purges the specified directory path.&nbsp;Use with great caution since
500 * no attempt is made to check for symbolic links, which could result in
501 * deletion of unintended files.
502 *
503 * @param dir directory to be removed
504 * @throws java.io.IOException if unable to remove contents
505 */
506 public static void removeDirectory(File dir) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800507 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700508 if (dir.exists() && dir.isDirectory()) {
509 walkFileTree(Paths.get(dir.getAbsolutePath()), 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
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800516 // Auxiliary path visitor for recursive directory structure removal.
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800517 private static class DirectoryDeleter extends SimpleFileVisitor<Path> {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800518
519 private IOException exception;
520
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800521 @Override
522 public FileVisitResult visitFile(Path file, BasicFileAttributes attributes)
523 throws IOException {
524 if (attributes.isRegularFile()) {
525 delete(file);
526 }
527 return FileVisitResult.CONTINUE;
528 }
529
530 @Override
531 public FileVisitResult postVisitDirectory(Path directory, IOException ioe)
532 throws IOException {
533 delete(directory);
534 return FileVisitResult.CONTINUE;
535 }
536
537 @Override
538 public FileVisitResult visitFileFailed(Path file, IOException ioe)
539 throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800540 this.exception = ioe;
541 return FileVisitResult.TERMINATE;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800542 }
543 }
544
Madan Jampani30a57f82015-03-02 12:19:41 -0800545 /**
546 * Returns a human friendly time ago string for a specified system time.
Thomas Vachuska480adad2015-03-06 10:27:09 -0800547 *
Madan Jampani30a57f82015-03-02 12:19:41 -0800548 * @param unixTime system time in millis
549 * @return human friendly time ago
550 */
551 public static String timeAgo(long unixTime) {
552 long deltaMillis = System.currentTimeMillis() - unixTime;
553 long secondsSince = (long) (deltaMillis / 1000.0);
554 long minsSince = (long) (deltaMillis / (1000.0 * 60));
555 long hoursSince = (long) (deltaMillis / (1000.0 * 60 * 60));
556 long daysSince = (long) (deltaMillis / (1000.0 * 60 * 60 * 24));
557 if (daysSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800558 return String.format("%dd%dh ago", daysSince, hoursSince - daysSince * 24);
Madan Jampani30a57f82015-03-02 12:19:41 -0800559 } else if (hoursSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800560 return String.format("%dh%dm ago", hoursSince, minsSince - hoursSince * 60);
Madan Jampani30a57f82015-03-02 12:19:41 -0800561 } else if (minsSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800562 return String.format("%dm%ds ago", minsSince, secondsSince - minsSince * 60);
Madan Jampani30a57f82015-03-02 12:19:41 -0800563 } else if (secondsSince > 0) {
564 return String.format("%ds ago", secondsSince);
565 } else {
566 return "just now";
567 }
568 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800569
570 /**
571 * Copies the specified directory path.&nbsp;Use with great caution since
572 * no attempt is made to check for symbolic links, which could result in
573 * copy of unintended files.
574 *
575 * @param src directory to be copied
576 * @param dst destination directory to be removed
577 * @throws java.io.IOException if unable to remove contents
578 */
579 public static void copyDirectory(String src, String dst) throws IOException {
580 walkFileTree(Paths.get(src), new DirectoryCopier(src, dst));
581 }
582
583 /**
584 * Copies the specified directory path.&nbsp;Use with great caution since
585 * no attempt is made to check for symbolic links, which could result in
586 * copy of unintended files.
587 *
588 * @param src directory to be copied
589 * @param dst destination directory to be removed
590 * @throws java.io.IOException if unable to remove contents
591 */
592 public static void copyDirectory(File src, File dst) throws IOException {
593 walkFileTree(Paths.get(src.getAbsolutePath()),
594 new DirectoryCopier(src.getAbsolutePath(),
595 dst.getAbsolutePath()));
596 }
597
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700598 /**
599 * Returns the future value when complete or if future
600 * completes exceptionally returns the defaultValue.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700601 *
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700602 * @param future future
603 * @param defaultValue default value
604 * @param <T> future value type
605 * @return future value when complete or if future
606 * completes exceptionally returns the defaultValue.
607 */
608 public static <T> T futureGetOrElse(Future<T> future, T defaultValue) {
609 try {
610 return future.get();
611 } catch (InterruptedException e) {
612 Thread.currentThread().interrupt();
613 return defaultValue;
614 } catch (ExecutionException e) {
615 return defaultValue;
616 }
617 }
618
619 /**
620 * Returns the future value when complete or if future
621 * completes exceptionally returns the defaultValue.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700622 *
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700623 * @param future future
624 * @param timeout time to wait for successful completion
625 * @param timeUnit time unit
626 * @param defaultValue default value
627 * @param <T> future value type
628 * @return future value when complete or if future
629 * completes exceptionally returns the defaultValue.
630 */
631 public static <T> T futureGetOrElse(Future<T> future,
632 long timeout,
633 TimeUnit timeUnit,
634 T defaultValue) {
635 try {
636 return future.get(timeout, timeUnit);
637 } catch (InterruptedException e) {
638 Thread.currentThread().interrupt();
639 return defaultValue;
640 } catch (ExecutionException | TimeoutException e) {
641 return defaultValue;
642 }
643 }
644
Madan Jampani27b69c62015-05-15 15:49:02 -0700645 /**
646 * Returns a future that is completed exceptionally.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700647 *
Madan Jampani27b69c62015-05-15 15:49:02 -0700648 * @param t exception
649 * @param <T> future value type
650 * @return future
651 */
652 public static <T> CompletableFuture<T> exceptionalFuture(Throwable t) {
653 CompletableFuture<T> future = new CompletableFuture<>();
654 future.completeExceptionally(t);
655 return future;
656 }
657
658 /**
Jordan Halterman046faeb2017-05-01 15:10:13 -0700659 * Returns a future that's completed using the given {@code orderedExecutor} if the future is not blocked or the
660 * given {@code threadPoolExecutor} if the future is blocked.
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700661 * <p>
Jordan Halterman046faeb2017-05-01 15:10:13 -0700662 * This method allows futures to maintain single-thread semantics via the provided {@code orderedExecutor} while
663 * ensuring user code can block without blocking completion of futures. When the returned future or any of its
664 * descendants is blocked on a {@link CompletableFuture#get()} or {@link CompletableFuture#join()} call, completion
665 * of the returned future will be done using the provided {@code threadPoolExecutor}.
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700666 *
667 * @param future the future to convert into an asynchronous future
Jordan Halterman046faeb2017-05-01 15:10:13 -0700668 * @param orderedExecutor the ordered executor with which to attempt to complete the future
669 * @param threadPoolExecutor the backup executor with which to complete blocked futures
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700670 * @param <T> future value type
671 * @return a new completable future to be completed using the provided {@code executor} once the provided
672 * {@code future} is complete
673 */
Jordan Halterman046faeb2017-05-01 15:10:13 -0700674 public static <T> CompletableFuture<T> orderedFuture(
675 CompletableFuture<T> future,
676 Executor orderedExecutor,
677 Executor threadPoolExecutor) {
Jordan Haltermane265d372017-05-17 22:40:47 -0700678 if (future.isDone()) {
679 return future;
680 }
681
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700682 BlockingAwareFuture<T> newFuture = new BlockingAwareFuture<>();
Jordan Halterman046faeb2017-05-01 15:10:13 -0700683 future.whenComplete((result, error) -> {
684 Runnable completer = () -> {
685 if (future.isCompletedExceptionally()) {
686 newFuture.completeExceptionally(error);
687 } else {
688 newFuture.complete(result);
689 }
690 };
691
692 if (newFuture.isBlocked()) {
693 threadPoolExecutor.execute(completer);
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700694 } else {
Jordan Halterman046faeb2017-05-01 15:10:13 -0700695 orderedExecutor.execute(completer);
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700696 }
Jordan Halterman046faeb2017-05-01 15:10:13 -0700697 });
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700698 return newFuture;
699 }
700
701 /**
Sho SHIMIZU85803e22016-01-13 21:53:43 -0800702 * Returns a new CompletableFuture completed with a list of computed values
703 * when all of the given CompletableFuture complete.
704 *
705 * @param futures the CompletableFutures
706 * @param <T> value type of CompletableFuture
707 * @return a new CompletableFuture that is completed when all of the given CompletableFutures complete
708 */
709 public static <T> CompletableFuture<List<T>> allOf(List<CompletableFuture<T>> futures) {
710 return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]))
711 .thenApply(v -> futures.stream()
712 .map(CompletableFuture::join)
713 .collect(Collectors.toList())
714 );
715 }
716
717 /**
Madan Jampani307a21e2016-09-01 15:49:47 -0700718 * Returns a new CompletableFuture completed by reducing a list of computed values
719 * when all of the given CompletableFuture complete.
720 *
721 * @param futures the CompletableFutures
722 * @param reducer reducer for computing the result
723 * @param emptyValue zero value to be returned if the input future list is empty
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<T> allOf(List<CompletableFuture<T>> futures,
728 BinaryOperator<T> reducer,
729 T emptyValue) {
730 return Tools.allOf(futures)
731 .thenApply(resultList -> resultList.stream().reduce(reducer).orElse(emptyValue));
732 }
733
734 /**
735 * Returns a new CompletableFuture completed by with the first positive result from a list of
736 * input CompletableFutures.
737 *
738 * @param futures the input list of CompletableFutures
739 * @param positiveResultMatcher matcher to identify a positive result
740 * @param negativeResult value to complete with if none of the futures complete with a positive result
741 * @param <T> value type of CompletableFuture
742 * @return a new CompletableFuture
743 */
744 public static <T> CompletableFuture<T> firstOf(List<CompletableFuture<T>> futures,
745 Match<T> positiveResultMatcher,
746 T negativeResult) {
747 CompletableFuture<T> responseFuture = new CompletableFuture<>();
748 Tools.allOf(Lists.transform(futures, future -> future.thenAccept(r -> {
749 if (positiveResultMatcher.matches(r)) {
750 responseFuture.complete(r);
751 }
752 }))).whenComplete((r, e) -> {
753 if (!responseFuture.isDone()) {
754 if (e != null) {
755 responseFuture.completeExceptionally(e);
756 } else {
757 responseFuture.complete(negativeResult);
758 }
759 }
760 });
761 return responseFuture;
762 }
763
764 /**
Madan Jampani27b69c62015-05-15 15:49:02 -0700765 * Returns the contents of {@code ByteBuffer} as byte array.
766 * <p>
767 * WARNING: There is a performance cost due to array copy
768 * when using this method.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700769 *
Madan Jampani27b69c62015-05-15 15:49:02 -0700770 * @param buffer byte buffer
771 * @return byte array containing the byte buffer contents
772 */
773 public static byte[] byteBuffertoArray(ByteBuffer buffer) {
774 int length = buffer.remaining();
775 if (buffer.hasArray()) {
776 int offset = buffer.arrayOffset() + buffer.position();
777 return Arrays.copyOfRange(buffer.array(), offset, offset + length);
778 }
779 byte[] bytes = new byte[length];
780 buffer.duplicate().get(bytes);
781 return bytes;
782 }
783
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700784 /**
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700785 * Converts an iterable to a stream.
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700786 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700787 * @param it iterable to convert
788 * @param <T> type if item
789 * @return iterable as a stream
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700790 */
791 public static <T> Stream<T> stream(Iterable<T> it) {
792 return StreamSupport.stream(it.spliterator(), false);
793 }
794
Sho SHIMIZUb5638b82016-02-11 14:55:05 -0800795 /**
796 * Converts an optional to a stream.
797 *
798 * @param optional optional to convert
799 * @param <T> type of enclosed value
800 * @return optional as a stream
801 */
Sho SHIMIZU6ac20982016-05-04 09:50:54 -0700802 public static <T> Stream<T> stream(Optional<? extends T> optional) {
HIGUCHI Yuta0bc256f2016-05-06 15:28:26 -0700803 return optional.map(x -> Stream.<T>of(x)).orElse(Stream.empty());
Sho SHIMIZUb5638b82016-02-11 14:55:05 -0800804 }
805
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800806 // Auxiliary path visitor for recursive directory structure copying.
807 private static class DirectoryCopier extends SimpleFileVisitor<Path> {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800808 private Path src;
809 private Path dst;
810 private StandardCopyOption copyOption = StandardCopyOption.REPLACE_EXISTING;
811
812 DirectoryCopier(String src, String dst) {
813 this.src = Paths.get(src);
814 this.dst = Paths.get(dst);
815 }
816
817 @Override
818 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
819 Path targetPath = dst.resolve(src.relativize(dir));
820 if (!Files.exists(targetPath)) {
821 Files.createDirectory(targetPath);
822 }
823 return FileVisitResult.CONTINUE;
824 }
825
826 @Override
827 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
828 Files.copy(file, dst.resolve(src.relativize(file)), copyOption);
829 return FileVisitResult.CONTINUE;
830 }
831 }
832
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700833 /**
834 * Creates OffsetDateTime instance from epoch milliseconds,
835 * using system default time zone.
836 *
837 * @param epochMillis to convert
838 * @return OffsetDateTime
839 */
840 public static OffsetDateTime defaultOffsetDataTime(long epochMillis) {
841 return OffsetDateTime.ofInstant(Instant.ofEpochMilli(epochMillis),
842 ZoneId.systemDefault());
843 }
844
tom5f38b3a2014-08-27 23:50:54 -0700845}