blob: a8c8a8bf48c38497471e466c002b197c38a1d150 [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
Yuta HIGUCHI47d96092017-11-17 14:05:26 -080059import static com.google.common.base.Preconditions.checkNotNull;
Jonathan Hartcc962d82016-08-09 16:52:22 -070060import static java.nio.file.Files.delete;
61import static java.nio.file.Files.walkFileTree;
62import static org.onlab.util.GroupedThreadFactory.groupedThreadFactory;
63import static org.slf4j.LoggerFactory.getLogger;
Ray Milkey705d9bc2014-11-18 08:19:00 -080064
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080065/**
66 * Miscellaneous utility methods.
67 */
tom5f38b3a2014-08-27 23:50:54 -070068public abstract class Tools {
69
70 private Tools() {
71 }
72
Thomas Vachuska02aeb032015-01-06 22:36:30 -080073 private static final Logger log = getLogger(Tools.class);
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080074
Thomas Vachuskaadba1522015-06-04 15:08:30 -070075 private static Random random = new Random();
76
tom5f38b3a2014-08-27 23:50:54 -070077 /**
78 * Returns a thread factory that produces threads named according to the
79 * supplied name pattern.
80 *
81 * @param pattern name pattern
82 * @return thread factory
83 */
84 public static ThreadFactory namedThreads(String pattern) {
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080085 return new ThreadFactoryBuilder()
86 .setNameFormat(pattern)
Thomas Vachuska480adad2015-03-06 10:27:09 -080087 .setUncaughtExceptionHandler((t, e) -> log.error("Uncaught exception on " + t.getName(), e))
88 .build();
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080089 }
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080090
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080091 /**
92 * Returns a thread factory that produces threads named according to the
93 * supplied name pattern and from the specified thread-group. The thread
94 * group name is expected to be specified in slash-delimited format, e.g.
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080095 * {@code onos/intent}. The thread names will be produced by converting
96 * the thread group name into dash-delimited format and pre-pended to the
97 * specified pattern.
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080098 *
99 * @param groupName group name in slash-delimited format to indicate hierarchy
100 * @param pattern name pattern
101 * @return thread factory
102 */
103 public static ThreadFactory groupedThreads(String groupName, String pattern) {
Jian Li03e9fb02016-03-01 17:13:54 -0800104 return groupedThreads(groupName, pattern, log);
105 }
106
107 /**
108 * Returns a thread factory that produces threads named according to the
109 * supplied name pattern and from the specified thread-group. The thread
110 * group name is expected to be specified in slash-delimited format, e.g.
111 * {@code onos/intent}. The thread names will be produced by converting
112 * the thread group name into dash-delimited format and pre-pended to the
113 * specified pattern. If a logger is specified, it will use the logger to
114 * print out the exception if it has any.
115 *
116 * @param groupName group name in slash-delimited format to indicate hierarchy
117 * @param pattern name pattern
118 * @param logger logger
119 * @return thread factory
120 */
121 public static ThreadFactory groupedThreads(String groupName, String pattern, Logger logger) {
122 if (logger == null) {
123 return groupedThreads(groupName, pattern);
124 }
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -0800125 return new ThreadFactoryBuilder()
126 .setThreadFactory(groupedThreadFactory(groupName))
Thomas Vachuskac13b90a2015-02-18 18:19:55 -0800127 .setNameFormat(groupName.replace(GroupedThreadFactory.DELIMITER, "-") + "-" + pattern)
Jian Li03e9fb02016-03-01 17:13:54 -0800128 .setUncaughtExceptionHandler((t, e) -> logger.error("Uncaught exception on " + t.getName(), e))
Thomas Vachuska480adad2015-03-06 10:27:09 -0800129 .build();
tom5f38b3a2014-08-27 23:50:54 -0700130 }
131
tom782a7cf2014-09-11 23:58:38 -0700132 /**
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800133 * Returns a thread factory that produces threads with MIN_PRIORITY.
134 *
135 * @param factory backing ThreadFactory
136 * @return thread factory
137 */
138 public static ThreadFactory minPriority(ThreadFactory factory) {
139 return new ThreadFactoryBuilder()
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800140 .setThreadFactory(factory)
141 .setPriority(Thread.MIN_PRIORITY)
142 .build();
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800143 }
144
145 /**
Yuta HIGUCHIa2a11cd2016-12-19 14:19:11 -0800146 * Returns a thread factory that produces threads with MAX_PRIORITY.
147 *
148 * @param factory backing ThreadFactory
149 * @return thread factory
150 */
151 public static ThreadFactory maxPriority(ThreadFactory factory) {
152 return new ThreadFactoryBuilder()
153 .setThreadFactory(factory)
154 .setPriority(Thread.MAX_PRIORITY)
155 .build();
156 }
157
158 /**
Brian O'Connore2eac102015-02-12 18:30:22 -0800159 * Returns true if the collection is null or is empty.
160 *
161 * @param collection collection to test
162 * @return true if null or empty; false otherwise
163 */
164 public static boolean isNullOrEmpty(Collection collection) {
165 return collection == null || collection.isEmpty();
166 }
167
168 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700169 * Returns the specified item if that item is not null; otherwise throws
Thomas Vachuskaca88bb72015-04-08 19:38:02 -0700170 * not found exception.
171 *
172 * @param item item to check
173 * @param message not found message
174 * @param <T> item type
175 * @return item if not null
176 * @throws org.onlab.util.ItemNotFoundException if item is null
177 */
178 public static <T> T nullIsNotFound(T item, String message) {
179 if (item == null) {
180 throw new ItemNotFoundException(message);
181 }
182 return item;
183 }
184
185 /**
Ray Milkey36992c82015-11-17 13:31:15 -0800186 * Returns the specified set if the set is not null and not empty;
187 * otherwise throws a not found exception.
188 *
189 * @param item set to check
190 * @param message not found message
191 * @param <T> Set item type
192 * @return item if not null and not empty
193 * @throws org.onlab.util.ItemNotFoundException if set is null or empty
194 */
195 public static <T> Set<T> emptyIsNotFound(Set<T> item, String message) {
196 if (item == null || item.isEmpty()) {
197 throw new ItemNotFoundException(message);
198 }
199 return item;
200 }
201
202 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700203 * Returns the specified item if that item is not null; otherwise throws
204 * bad argument exception.
205 *
206 * @param item item to check
207 * @param message not found message
208 * @param <T> item type
209 * @return item if not null
210 * @throws IllegalArgumentException if item is null
211 */
212 public static <T> T nullIsIllegal(T item, String message) {
213 if (item == null) {
214 throw new IllegalArgumentException(message);
215 }
216 return item;
217 }
218
219 /**
tom782a7cf2014-09-11 23:58:38 -0700220 * Converts a string from hex to long.
221 *
222 * @param string hex number in string form; sans 0x
223 * @return long value
224 */
225 public static long fromHex(String string) {
226 return UnsignedLongs.parseUnsignedLong(string, 16);
227 }
228
229 /**
230 * Converts a long value to hex string; 16 wide and sans 0x.
231 *
232 * @param value long value
233 * @return hex string
234 */
235 public static String toHex(long value) {
236 return Strings.padStart(UnsignedLongs.toString(value, 16), 16, '0');
237 }
238
239 /**
240 * Converts a long value to hex string; 16 wide and sans 0x.
241 *
242 * @param value long value
243 * @param width string width; zero padded
244 * @return hex string
245 */
246 public static String toHex(long value, int width) {
247 return Strings.padStart(UnsignedLongs.toString(value, 16), width, '0');
248 }
tomf110fff2014-09-26 00:38:18 -0700249
250 /**
Jonathan Hartcc962d82016-08-09 16:52:22 -0700251 * Returns a string encoding in hex of the given long value with prefix
252 * '0x'.
253 *
254 * @param value long value to encode as hex string
255 * @return hex string
256 */
257 public static String toHexWithPrefix(long value) {
258 return "0x" + Long.toHexString(value);
259 }
260
261 /**
Madan Jampanif2f086c2016-01-13 16:15:39 -0800262 * Returns the UTF-8 encoded byte[] representation of a String.
Jian Lidfba7392016-01-22 16:46:58 -0800263 * @param input input string
264 * @return UTF-8 encoded byte array
Madan Jampanif2f086c2016-01-13 16:15:39 -0800265 */
266 public static byte[] getBytesUtf8(String input) {
267 return input.getBytes(Charsets.UTF_8);
268 }
269
270 /**
271 * Returns the String representation of UTF-8 encoded byte[].
Jian Lidfba7392016-01-22 16:46:58 -0800272 * @param input input byte array
273 * @return UTF-8 encoded string
Madan Jampanif2f086c2016-01-13 16:15:39 -0800274 */
275 public static String toStringUtf8(byte[] input) {
276 return new String(input, Charsets.UTF_8);
277 }
278
279 /**
Madan Jampani9eb55d12015-08-14 07:47:56 -0700280 * Returns a copy of the input byte array.
281 *
282 * @param original input
283 * @return copy of original
284 */
285 public static byte[] copyOf(byte[] original) {
286 return Arrays.copyOf(original, original.length);
287 }
288
289 /**
Thomas Vachuska6519e6f2015-03-11 02:29:31 -0700290 * Get property as a string value.
291 *
292 * @param properties properties to be looked up
293 * @param propertyName the name of the property to look up
294 * @return value when the propertyName is defined or return null
295 */
296 public static String get(Dictionary<?, ?> properties, String propertyName) {
297 Object v = properties.get(propertyName);
298 String s = (v instanceof String) ? (String) v :
299 v != null ? v.toString() : null;
300 return Strings.isNullOrEmpty(s) ? null : s.trim();
301 }
302
303 /**
Jian Lid9b5f552016-03-11 18:15:31 -0800304 * Get Integer property from the propertyName
305 * Return null if propertyName is not found.
306 *
307 * @param properties properties to be looked up
308 * @param propertyName the name of the property to look up
309 * @return value when the propertyName is defined or return null
310 */
311 public static Integer getIntegerProperty(Dictionary<?, ?> properties,
312 String propertyName) {
313 Integer value;
314 try {
315 String s = get(properties, propertyName);
316 value = Strings.isNullOrEmpty(s) ? null : Integer.valueOf(s);
317 } catch (NumberFormatException | ClassCastException e) {
318 value = null;
319 }
320 return value;
321 }
322
323 /**
324 * Get Integer property from the propertyName
325 * Return default value if propertyName is not found.
326 *
327 * @param properties properties to be looked up
328 * @param propertyName the name of the property to look up
329 * @param defaultValue the default value that to be assigned
330 * @return value when the propertyName is defined or return default value
331 */
332 public static int getIntegerProperty(Dictionary<?, ?> properties,
333 String propertyName,
334 int defaultValue) {
335 try {
336 String s = get(properties, propertyName);
337 return Strings.isNullOrEmpty(s) ? defaultValue : Integer.valueOf(s);
338 } catch (NumberFormatException | ClassCastException e) {
339 return defaultValue;
340 }
341 }
342
343 /**
344 * Check property name is defined and set to true.
345 *
346 * @param properties properties to be looked up
347 * @param propertyName the name of the property to look up
348 * @return value when the propertyName is defined or return null
349 */
350 public static Boolean isPropertyEnabled(Dictionary<?, ?> properties,
351 String propertyName) {
352 Boolean value;
353 try {
354 String s = get(properties, propertyName);
355 value = Strings.isNullOrEmpty(s) ? null : Boolean.valueOf(s);
356 } catch (ClassCastException e) {
357 value = null;
358 }
359 return value;
360 }
361
362 /**
363 * Check property name is defined as set to true.
364 *
365 * @param properties properties to be looked up
366 * @param propertyName the name of the property to look up
367 * @param defaultValue the default value that to be assigned
368 * @return value when the propertyName is defined or return the default value
369 */
370 public static boolean isPropertyEnabled(Dictionary<?, ?> properties,
371 String propertyName,
372 boolean defaultValue) {
373 try {
374 String s = get(properties, propertyName);
375 return Strings.isNullOrEmpty(s) ? defaultValue : Boolean.valueOf(s);
376 } catch (ClassCastException e) {
377 return defaultValue;
378 }
379 }
380
381 /**
tomf110fff2014-09-26 00:38:18 -0700382 * Suspends the current thread for a specified number of millis.
383 *
384 * @param ms number of millis
385 */
386 public static void delay(int ms) {
387 try {
388 Thread.sleep(ms);
389 } catch (InterruptedException e) {
390 throw new RuntimeException("Interrupted", e);
391 }
392 }
393
tom53efab52014-10-07 17:43:48 -0700394 /**
sdn94b00152016-08-30 02:12:32 -0700395 * Get Long property from the propertyName
396 * Return null if propertyName is not found.
397 *
398 * @param properties properties to be looked up
399 * @param propertyName the name of the property to look up
400 * @return value when the propertyName is defined or return null
401 */
402 public static Long getLongProperty(Dictionary<?, ?> properties,
403 String propertyName) {
404 Long value;
405 try {
406 String s = get(properties, propertyName);
407 value = Strings.isNullOrEmpty(s) ? null : Long.valueOf(s);
408 } catch (NumberFormatException | ClassCastException e) {
409 value = null;
410 }
411 return value;
412 }
413
414 /**
Madan Jampania29c6772015-08-17 13:17:07 -0700415 * Returns a function that retries execution on failure.
416 * @param base base function
417 * @param exceptionClass type of exception for which to retry
418 * @param maxRetries max number of retries before giving up
419 * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from
420 * the interval (0, maxDelayBetweenRetries]
421 * @return function
Thomas Vachuska87ae1d92015-08-19 17:39:11 -0700422 * @param <U> type of function input
423 * @param <V> type of function output
Madan Jampania29c6772015-08-17 13:17:07 -0700424 */
425 public static <U, V> Function<U, V> retryable(Function<U, V> base,
426 Class<? extends Throwable> exceptionClass,
427 int maxRetries,
428 int maxDelayBetweenRetries) {
429 return new RetryingFunction<>(base, exceptionClass, maxRetries, maxDelayBetweenRetries);
430 }
431
432 /**
433 * Returns a Supplier that retries execution on failure.
434 * @param base base supplier
435 * @param exceptionClass type of exception for which to retry
436 * @param maxRetries max number of retries before giving up
437 * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from
438 * the interval (0, maxDelayBetweenRetries]
439 * @return supplier
Thomas Vachuska87ae1d92015-08-19 17:39:11 -0700440 * @param <V> type of supplied result
Madan Jampania29c6772015-08-17 13:17:07 -0700441 */
442 public static <V> Supplier<V> retryable(Supplier<V> base,
443 Class<? extends Throwable> exceptionClass,
444 int maxRetries,
445 int maxDelayBetweenRetries) {
446 return () -> new RetryingFunction<>(v -> base.get(),
447 exceptionClass,
448 maxRetries,
449 maxDelayBetweenRetries).apply(null);
450 }
451
452 /**
Thomas Vachuskaadba1522015-06-04 15:08:30 -0700453 * Suspends the current thread for a random number of millis between 0 and
454 * the indicated limit.
455 *
456 * @param ms max number of millis
457 */
458 public static void randomDelay(int ms) {
459 try {
460 Thread.sleep(random.nextInt(ms));
461 } catch (InterruptedException e) {
462 throw new RuntimeException("Interrupted", e);
463 }
464 }
465
466 /**
Thomas Vachuskac40d4632015-04-09 16:55:03 -0700467 * Suspends the current thread for a specified number of millis and nanos.
468 *
469 * @param ms number of millis
470 * @param nanos number of nanos
471 */
472 public static void delay(int ms, int nanos) {
473 try {
474 Thread.sleep(ms, nanos);
475 } catch (InterruptedException e) {
476 throw new RuntimeException("Interrupted", e);
477 }
478 }
479
480 /**
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800481 * Purges the specified directory path.&nbsp;Use with great caution since
482 * no attempt is made to check for symbolic links, which could result in
483 * deletion of unintended files.
484 *
485 * @param path directory to be removed
486 * @throws java.io.IOException if unable to remove contents
487 */
488 public static void removeDirectory(String path) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800489 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700490 File dir = new File(path);
491 if (dir.exists() && dir.isDirectory()) {
492 walkFileTree(Paths.get(path), visitor);
493 if (visitor.exception != null) {
494 throw visitor.exception;
495 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800496 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800497 }
498
499 /**
500 * Purges the specified directory path.&nbsp;Use with great caution since
501 * no attempt is made to check for symbolic links, which could result in
502 * deletion of unintended files.
503 *
504 * @param dir directory to be removed
505 * @throws java.io.IOException if unable to remove contents
506 */
507 public static void removeDirectory(File dir) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800508 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700509 if (dir.exists() && dir.isDirectory()) {
510 walkFileTree(Paths.get(dir.getAbsolutePath()), visitor);
511 if (visitor.exception != null) {
512 throw visitor.exception;
513 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800514 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800515 }
516
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800517 // Auxiliary path visitor for recursive directory structure removal.
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800518 private static class DirectoryDeleter extends SimpleFileVisitor<Path> {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800519
520 private IOException exception;
521
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800522 @Override
523 public FileVisitResult visitFile(Path file, BasicFileAttributes attributes)
524 throws IOException {
525 if (attributes.isRegularFile()) {
526 delete(file);
527 }
528 return FileVisitResult.CONTINUE;
529 }
530
531 @Override
532 public FileVisitResult postVisitDirectory(Path directory, IOException ioe)
533 throws IOException {
534 delete(directory);
535 return FileVisitResult.CONTINUE;
536 }
537
538 @Override
539 public FileVisitResult visitFileFailed(Path file, IOException ioe)
540 throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800541 this.exception = ioe;
542 return FileVisitResult.TERMINATE;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800543 }
544 }
545
Madan Jampani30a57f82015-03-02 12:19:41 -0800546 /**
547 * Returns a human friendly time ago string for a specified system time.
Thomas Vachuska480adad2015-03-06 10:27:09 -0800548 *
Madan Jampani30a57f82015-03-02 12:19:41 -0800549 * @param unixTime system time in millis
550 * @return human friendly time ago
551 */
552 public static String timeAgo(long unixTime) {
553 long deltaMillis = System.currentTimeMillis() - unixTime;
554 long secondsSince = (long) (deltaMillis / 1000.0);
555 long minsSince = (long) (deltaMillis / (1000.0 * 60));
556 long hoursSince = (long) (deltaMillis / (1000.0 * 60 * 60));
557 long daysSince = (long) (deltaMillis / (1000.0 * 60 * 60 * 24));
558 if (daysSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800559 return String.format("%dd%dh ago", daysSince, hoursSince - daysSince * 24);
Madan Jampani30a57f82015-03-02 12:19:41 -0800560 } else if (hoursSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800561 return String.format("%dh%dm ago", hoursSince, minsSince - hoursSince * 60);
Madan Jampani30a57f82015-03-02 12:19:41 -0800562 } else if (minsSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800563 return String.format("%dm%ds ago", minsSince, secondsSince - minsSince * 60);
Madan Jampani30a57f82015-03-02 12:19:41 -0800564 } else if (secondsSince > 0) {
565 return String.format("%ds ago", secondsSince);
566 } else {
567 return "just now";
568 }
569 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800570
571 /**
572 * Copies the specified directory path.&nbsp;Use with great caution since
573 * no attempt is made to check for symbolic links, which could result in
574 * copy of unintended files.
575 *
576 * @param src directory to be copied
577 * @param dst destination directory to be removed
578 * @throws java.io.IOException if unable to remove contents
579 */
580 public static void copyDirectory(String src, String dst) throws IOException {
581 walkFileTree(Paths.get(src), new DirectoryCopier(src, dst));
582 }
583
584 /**
585 * Copies the specified directory path.&nbsp;Use with great caution since
586 * no attempt is made to check for symbolic links, which could result in
587 * copy of unintended files.
588 *
589 * @param src directory to be copied
590 * @param dst destination directory to be removed
591 * @throws java.io.IOException if unable to remove contents
592 */
593 public static void copyDirectory(File src, File dst) throws IOException {
594 walkFileTree(Paths.get(src.getAbsolutePath()),
595 new DirectoryCopier(src.getAbsolutePath(),
596 dst.getAbsolutePath()));
597 }
598
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700599 /**
600 * Returns the future value when complete or if future
601 * completes exceptionally returns the defaultValue.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700602 *
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700603 * @param future future
604 * @param defaultValue default value
605 * @param <T> future value type
606 * @return future value when complete or if future
607 * completes exceptionally returns the defaultValue.
608 */
609 public static <T> T futureGetOrElse(Future<T> future, T defaultValue) {
610 try {
611 return future.get();
612 } catch (InterruptedException e) {
613 Thread.currentThread().interrupt();
614 return defaultValue;
615 } catch (ExecutionException e) {
616 return defaultValue;
617 }
618 }
619
620 /**
621 * Returns the future value when complete or if future
622 * completes exceptionally returns the defaultValue.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700623 *
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700624 * @param future future
625 * @param timeout time to wait for successful completion
626 * @param timeUnit time unit
627 * @param defaultValue default value
628 * @param <T> future value type
629 * @return future value when complete or if future
630 * completes exceptionally returns the defaultValue.
631 */
632 public static <T> T futureGetOrElse(Future<T> future,
633 long timeout,
634 TimeUnit timeUnit,
635 T defaultValue) {
636 try {
637 return future.get(timeout, timeUnit);
638 } catch (InterruptedException e) {
639 Thread.currentThread().interrupt();
640 return defaultValue;
641 } catch (ExecutionException | TimeoutException e) {
642 return defaultValue;
643 }
644 }
645
Madan Jampani27b69c62015-05-15 15:49:02 -0700646 /**
647 * Returns a future that is completed exceptionally.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700648 *
Madan Jampani27b69c62015-05-15 15:49:02 -0700649 * @param t exception
650 * @param <T> future value type
651 * @return future
652 */
653 public static <T> CompletableFuture<T> exceptionalFuture(Throwable t) {
654 CompletableFuture<T> future = new CompletableFuture<>();
655 future.completeExceptionally(t);
656 return future;
657 }
658
659 /**
Jordan Halterman046faeb2017-05-01 15:10:13 -0700660 * Returns a future that's completed using the given {@code orderedExecutor} if the future is not blocked or the
661 * given {@code threadPoolExecutor} if the future is blocked.
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700662 * <p>
Jordan Halterman046faeb2017-05-01 15:10:13 -0700663 * This method allows futures to maintain single-thread semantics via the provided {@code orderedExecutor} while
664 * ensuring user code can block without blocking completion of futures. When the returned future or any of its
665 * descendants is blocked on a {@link CompletableFuture#get()} or {@link CompletableFuture#join()} call, completion
666 * of the returned future will be done using the provided {@code threadPoolExecutor}.
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700667 *
668 * @param future the future to convert into an asynchronous future
Jordan Halterman046faeb2017-05-01 15:10:13 -0700669 * @param orderedExecutor the ordered executor with which to attempt to complete the future
670 * @param threadPoolExecutor the backup executor with which to complete blocked futures
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700671 * @param <T> future value type
672 * @return a new completable future to be completed using the provided {@code executor} once the provided
673 * {@code future} is complete
674 */
Jordan Halterman046faeb2017-05-01 15:10:13 -0700675 public static <T> CompletableFuture<T> orderedFuture(
676 CompletableFuture<T> future,
677 Executor orderedExecutor,
678 Executor threadPoolExecutor) {
Jordan Haltermane265d372017-05-17 22:40:47 -0700679 if (future.isDone()) {
680 return future;
681 }
682
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700683 BlockingAwareFuture<T> newFuture = new BlockingAwareFuture<>();
Jordan Halterman046faeb2017-05-01 15:10:13 -0700684 future.whenComplete((result, error) -> {
685 Runnable completer = () -> {
686 if (future.isCompletedExceptionally()) {
687 newFuture.completeExceptionally(error);
688 } else {
689 newFuture.complete(result);
690 }
691 };
692
693 if (newFuture.isBlocked()) {
694 threadPoolExecutor.execute(completer);
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700695 } else {
Jordan Halterman046faeb2017-05-01 15:10:13 -0700696 orderedExecutor.execute(completer);
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700697 }
Jordan Halterman046faeb2017-05-01 15:10:13 -0700698 });
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700699 return newFuture;
700 }
701
702 /**
Sho SHIMIZU85803e22016-01-13 21:53:43 -0800703 * Returns a new CompletableFuture completed with a list of computed values
704 * when all of the given CompletableFuture complete.
705 *
706 * @param futures the CompletableFutures
707 * @param <T> value type of CompletableFuture
708 * @return a new CompletableFuture that is completed when all of the given CompletableFutures complete
709 */
710 public static <T> CompletableFuture<List<T>> allOf(List<CompletableFuture<T>> futures) {
711 return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]))
712 .thenApply(v -> futures.stream()
713 .map(CompletableFuture::join)
714 .collect(Collectors.toList())
715 );
716 }
717
718 /**
Madan Jampani307a21e2016-09-01 15:49:47 -0700719 * Returns a new CompletableFuture completed by reducing a list of computed values
720 * when all of the given CompletableFuture complete.
721 *
722 * @param futures the CompletableFutures
723 * @param reducer reducer for computing the result
724 * @param emptyValue zero value to be returned if the input future list is empty
725 * @param <T> value type of CompletableFuture
726 * @return a new CompletableFuture that is completed when all of the given CompletableFutures complete
727 */
728 public static <T> CompletableFuture<T> allOf(List<CompletableFuture<T>> futures,
729 BinaryOperator<T> reducer,
730 T emptyValue) {
731 return Tools.allOf(futures)
732 .thenApply(resultList -> resultList.stream().reduce(reducer).orElse(emptyValue));
733 }
734
735 /**
736 * Returns a new CompletableFuture completed by with the first positive result from a list of
737 * input CompletableFutures.
738 *
739 * @param futures the input list of CompletableFutures
740 * @param positiveResultMatcher matcher to identify a positive result
741 * @param negativeResult value to complete with if none of the futures complete with a positive result
742 * @param <T> value type of CompletableFuture
743 * @return a new CompletableFuture
744 */
745 public static <T> CompletableFuture<T> firstOf(List<CompletableFuture<T>> futures,
746 Match<T> positiveResultMatcher,
747 T negativeResult) {
748 CompletableFuture<T> responseFuture = new CompletableFuture<>();
749 Tools.allOf(Lists.transform(futures, future -> future.thenAccept(r -> {
750 if (positiveResultMatcher.matches(r)) {
751 responseFuture.complete(r);
752 }
753 }))).whenComplete((r, e) -> {
754 if (!responseFuture.isDone()) {
755 if (e != null) {
756 responseFuture.completeExceptionally(e);
757 } else {
758 responseFuture.complete(negativeResult);
759 }
760 }
761 });
762 return responseFuture;
763 }
764
765 /**
Madan Jampani27b69c62015-05-15 15:49:02 -0700766 * Returns the contents of {@code ByteBuffer} as byte array.
767 * <p>
768 * WARNING: There is a performance cost due to array copy
769 * when using this method.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700770 *
Madan Jampani27b69c62015-05-15 15:49:02 -0700771 * @param buffer byte buffer
772 * @return byte array containing the byte buffer contents
773 */
774 public static byte[] byteBuffertoArray(ByteBuffer buffer) {
775 int length = buffer.remaining();
776 if (buffer.hasArray()) {
777 int offset = buffer.arrayOffset() + buffer.position();
778 return Arrays.copyOfRange(buffer.array(), offset, offset + length);
779 }
780 byte[] bytes = new byte[length];
781 buffer.duplicate().get(bytes);
782 return bytes;
783 }
784
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700785 /**
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700786 * Converts an iterable to a stream.
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700787 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700788 * @param it iterable to convert
789 * @param <T> type if item
790 * @return iterable as a stream
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700791 */
792 public static <T> Stream<T> stream(Iterable<T> it) {
793 return StreamSupport.stream(it.spliterator(), false);
794 }
795
Sho SHIMIZUb5638b82016-02-11 14:55:05 -0800796 /**
797 * Converts an optional to a stream.
798 *
799 * @param optional optional to convert
800 * @param <T> type of enclosed value
801 * @return optional as a stream
802 */
Sho SHIMIZU6ac20982016-05-04 09:50:54 -0700803 public static <T> Stream<T> stream(Optional<? extends T> optional) {
HIGUCHI Yuta0bc256f2016-05-06 15:28:26 -0700804 return optional.map(x -> Stream.<T>of(x)).orElse(Stream.empty());
Sho SHIMIZUb5638b82016-02-11 14:55:05 -0800805 }
806
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800807 // Auxiliary path visitor for recursive directory structure copying.
808 private static class DirectoryCopier extends SimpleFileVisitor<Path> {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800809 private Path src;
810 private Path dst;
811 private StandardCopyOption copyOption = StandardCopyOption.REPLACE_EXISTING;
812
813 DirectoryCopier(String src, String dst) {
814 this.src = Paths.get(src);
815 this.dst = Paths.get(dst);
816 }
817
818 @Override
819 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
820 Path targetPath = dst.resolve(src.relativize(dir));
821 if (!Files.exists(targetPath)) {
822 Files.createDirectory(targetPath);
823 }
824 return FileVisitResult.CONTINUE;
825 }
826
827 @Override
828 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
829 Files.copy(file, dst.resolve(src.relativize(file)), copyOption);
830 return FileVisitResult.CONTINUE;
831 }
832 }
833
Yuta HIGUCHI0c47d532017-08-18 23:16:35 -0700834 /**
835 * Creates OffsetDateTime instance from epoch milliseconds,
836 * using system default time zone.
837 *
838 * @param epochMillis to convert
839 * @return OffsetDateTime
840 */
841 public static OffsetDateTime defaultOffsetDataTime(long epochMillis) {
842 return OffsetDateTime.ofInstant(Instant.ofEpochMilli(epochMillis),
843 ZoneId.systemDefault());
844 }
845
Yuta HIGUCHI47d96092017-11-17 14:05:26 -0800846 /**
847 * Returns smaller of the two Comparable values.
848 *
849 * @param l an argument
850 * @param r another argument
851 * @return the smaller of {@code l} or {@code r}
852 * @param <C> Comparable type
853 * @throws NullPointerException if any of the arguments were null.
854 */
855 public static <C extends Comparable<? super C>> C min(C l, C r) {
856 checkNotNull(l, "l cannot be null");
857 checkNotNull(r, "r cannot be null");
858 return l.compareTo(r) <= 0 ? l : r;
859 }
860
861 /**
862 * Returns larger of the two Comparable values.
863 *
864 * @param l an argument
865 * @param r another argument
866 * @return the larger of {@code l} or {@code r}
867 * @param <C> Comparable type
868 * @throws NullPointerException if any of the arguments were null.
869 */
870 public static <C extends Comparable<? super C>> C max(C l, C r) {
871 checkNotNull(l, "l cannot be null");
872 checkNotNull(r, "r cannot be null");
873 return l.compareTo(r) >= 0 ? l : r;
874 }
tom5f38b3a2014-08-27 23:50:54 -0700875}