blob: c4fcf051b49a89f83add28a74825c08fba7f9e09 [file] [log] [blame]
Thomas Vachuska24c849c2014-10-27 09:53:05 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2014-present Open Networking Laboratory
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;
Madan Jampani27b69c62015-05-15 15:49:02 -070035import java.util.Arrays;
Brian O'Connore2eac102015-02-12 18:30:22 -080036import java.util.Collection;
Thomas Vachuska6519e6f2015-03-11 02:29:31 -070037import java.util.Dictionary;
tom53efab52014-10-07 17:43:48 -070038import java.util.List;
Sho SHIMIZUb5638b82016-02-11 14:55:05 -080039import java.util.Optional;
Thomas Vachuskaadba1522015-06-04 15:08:30 -070040import java.util.Random;
Ray Milkey36992c82015-11-17 13:31:15 -080041import java.util.Set;
Madan Jampani27b69c62015-05-15 15:49:02 -070042import java.util.concurrent.CompletableFuture;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070043import java.util.concurrent.ExecutionException;
Jordan Halterman9bdc24f2017-04-19 23:45:12 -070044import java.util.concurrent.Executor;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070045import java.util.concurrent.Future;
tom5f38b3a2014-08-27 23:50:54 -070046import java.util.concurrent.ThreadFactory;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070047import java.util.concurrent.TimeUnit;
48import java.util.concurrent.TimeoutException;
Madan Jampani307a21e2016-09-01 15:49:47 -070049import java.util.function.BinaryOperator;
Madan Jampania29c6772015-08-17 13:17:07 -070050import java.util.function.Function;
51import java.util.function.Supplier;
Sho SHIMIZU85803e22016-01-13 21:53:43 -080052import java.util.stream.Collectors;
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -070053import java.util.stream.Stream;
54import java.util.stream.StreamSupport;
tom5f38b3a2014-08-27 23:50:54 -070055
Jonathan Hartcc962d82016-08-09 16:52:22 -070056import static java.nio.file.Files.delete;
57import static java.nio.file.Files.walkFileTree;
58import static org.onlab.util.GroupedThreadFactory.groupedThreadFactory;
59import static org.slf4j.LoggerFactory.getLogger;
Ray Milkey705d9bc2014-11-18 08:19:00 -080060
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080061/**
62 * Miscellaneous utility methods.
63 */
tom5f38b3a2014-08-27 23:50:54 -070064public abstract class Tools {
65
66 private Tools() {
67 }
68
Thomas Vachuska02aeb032015-01-06 22:36:30 -080069 private static final Logger log = getLogger(Tools.class);
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080070
Thomas Vachuskaadba1522015-06-04 15:08:30 -070071 private static Random random = new Random();
72
tom5f38b3a2014-08-27 23:50:54 -070073 /**
74 * Returns a thread factory that produces threads named according to the
75 * supplied name pattern.
76 *
77 * @param pattern name pattern
78 * @return thread factory
79 */
80 public static ThreadFactory namedThreads(String pattern) {
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080081 return new ThreadFactoryBuilder()
82 .setNameFormat(pattern)
Thomas Vachuska480adad2015-03-06 10:27:09 -080083 .setUncaughtExceptionHandler((t, e) -> log.error("Uncaught exception on " + t.getName(), e))
84 .build();
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080085 }
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080086
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080087 /**
88 * Returns a thread factory that produces threads named according to the
89 * supplied name pattern and from the specified thread-group. The thread
90 * group name is expected to be specified in slash-delimited format, e.g.
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080091 * {@code onos/intent}. The thread names will be produced by converting
92 * the thread group name into dash-delimited format and pre-pended to the
93 * specified pattern.
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080094 *
95 * @param groupName group name in slash-delimited format to indicate hierarchy
96 * @param pattern name pattern
97 * @return thread factory
98 */
99 public static ThreadFactory groupedThreads(String groupName, String pattern) {
Jian Li03e9fb02016-03-01 17:13:54 -0800100 return groupedThreads(groupName, pattern, log);
101 }
102
103 /**
104 * Returns a thread factory that produces threads named according to the
105 * supplied name pattern and from the specified thread-group. The thread
106 * group name is expected to be specified in slash-delimited format, e.g.
107 * {@code onos/intent}. The thread names will be produced by converting
108 * the thread group name into dash-delimited format and pre-pended to the
109 * specified pattern. If a logger is specified, it will use the logger to
110 * print out the exception if it has any.
111 *
112 * @param groupName group name in slash-delimited format to indicate hierarchy
113 * @param pattern name pattern
114 * @param logger logger
115 * @return thread factory
116 */
117 public static ThreadFactory groupedThreads(String groupName, String pattern, Logger logger) {
118 if (logger == null) {
119 return groupedThreads(groupName, pattern);
120 }
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -0800121 return new ThreadFactoryBuilder()
122 .setThreadFactory(groupedThreadFactory(groupName))
Thomas Vachuskac13b90a2015-02-18 18:19:55 -0800123 .setNameFormat(groupName.replace(GroupedThreadFactory.DELIMITER, "-") + "-" + pattern)
Jian Li03e9fb02016-03-01 17:13:54 -0800124 .setUncaughtExceptionHandler((t, e) -> logger.error("Uncaught exception on " + t.getName(), e))
Thomas Vachuska480adad2015-03-06 10:27:09 -0800125 .build();
tom5f38b3a2014-08-27 23:50:54 -0700126 }
127
tom782a7cf2014-09-11 23:58:38 -0700128 /**
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800129 * Returns a thread factory that produces threads with MIN_PRIORITY.
130 *
131 * @param factory backing ThreadFactory
132 * @return thread factory
133 */
134 public static ThreadFactory minPriority(ThreadFactory factory) {
135 return new ThreadFactoryBuilder()
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800136 .setThreadFactory(factory)
137 .setPriority(Thread.MIN_PRIORITY)
138 .build();
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800139 }
140
141 /**
Yuta HIGUCHIa2a11cd2016-12-19 14:19:11 -0800142 * Returns a thread factory that produces threads with MAX_PRIORITY.
143 *
144 * @param factory backing ThreadFactory
145 * @return thread factory
146 */
147 public static ThreadFactory maxPriority(ThreadFactory factory) {
148 return new ThreadFactoryBuilder()
149 .setThreadFactory(factory)
150 .setPriority(Thread.MAX_PRIORITY)
151 .build();
152 }
153
154 /**
Brian O'Connore2eac102015-02-12 18:30:22 -0800155 * Returns true if the collection is null or is empty.
156 *
157 * @param collection collection to test
158 * @return true if null or empty; false otherwise
159 */
160 public static boolean isNullOrEmpty(Collection collection) {
161 return collection == null || collection.isEmpty();
162 }
163
164 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700165 * Returns the specified item if that item is not null; otherwise throws
Thomas Vachuskaca88bb72015-04-08 19:38:02 -0700166 * not found exception.
167 *
168 * @param item item to check
169 * @param message not found message
170 * @param <T> item type
171 * @return item if not null
172 * @throws org.onlab.util.ItemNotFoundException if item is null
173 */
174 public static <T> T nullIsNotFound(T item, String message) {
175 if (item == null) {
176 throw new ItemNotFoundException(message);
177 }
178 return item;
179 }
180
181 /**
Ray Milkey36992c82015-11-17 13:31:15 -0800182 * Returns the specified set if the set is not null and not empty;
183 * otherwise throws a not found exception.
184 *
185 * @param item set to check
186 * @param message not found message
187 * @param <T> Set item type
188 * @return item if not null and not empty
189 * @throws org.onlab.util.ItemNotFoundException if set is null or empty
190 */
191 public static <T> Set<T> emptyIsNotFound(Set<T> item, String message) {
192 if (item == null || item.isEmpty()) {
193 throw new ItemNotFoundException(message);
194 }
195 return item;
196 }
197
198 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700199 * Returns the specified item if that item is not null; otherwise throws
200 * bad argument exception.
201 *
202 * @param item item to check
203 * @param message not found message
204 * @param <T> item type
205 * @return item if not null
206 * @throws IllegalArgumentException if item is null
207 */
208 public static <T> T nullIsIllegal(T item, String message) {
209 if (item == null) {
210 throw new IllegalArgumentException(message);
211 }
212 return item;
213 }
214
215 /**
tom782a7cf2014-09-11 23:58:38 -0700216 * Converts a string from hex to long.
217 *
218 * @param string hex number in string form; sans 0x
219 * @return long value
220 */
221 public static long fromHex(String string) {
222 return UnsignedLongs.parseUnsignedLong(string, 16);
223 }
224
225 /**
226 * Converts a long value to hex string; 16 wide and sans 0x.
227 *
228 * @param value long value
229 * @return hex string
230 */
231 public static String toHex(long value) {
232 return Strings.padStart(UnsignedLongs.toString(value, 16), 16, '0');
233 }
234
235 /**
236 * Converts a long value to hex string; 16 wide and sans 0x.
237 *
238 * @param value long value
239 * @param width string width; zero padded
240 * @return hex string
241 */
242 public static String toHex(long value, int width) {
243 return Strings.padStart(UnsignedLongs.toString(value, 16), width, '0');
244 }
tomf110fff2014-09-26 00:38:18 -0700245
246 /**
Jonathan Hartcc962d82016-08-09 16:52:22 -0700247 * Returns a string encoding in hex of the given long value with prefix
248 * '0x'.
249 *
250 * @param value long value to encode as hex string
251 * @return hex string
252 */
253 public static String toHexWithPrefix(long value) {
254 return "0x" + Long.toHexString(value);
255 }
256
257 /**
Madan Jampanif2f086c2016-01-13 16:15:39 -0800258 * Returns the UTF-8 encoded byte[] representation of a String.
Jian Lidfba7392016-01-22 16:46:58 -0800259 * @param input input string
260 * @return UTF-8 encoded byte array
Madan Jampanif2f086c2016-01-13 16:15:39 -0800261 */
262 public static byte[] getBytesUtf8(String input) {
263 return input.getBytes(Charsets.UTF_8);
264 }
265
266 /**
267 * Returns the String representation of UTF-8 encoded byte[].
Jian Lidfba7392016-01-22 16:46:58 -0800268 * @param input input byte array
269 * @return UTF-8 encoded string
Madan Jampanif2f086c2016-01-13 16:15:39 -0800270 */
271 public static String toStringUtf8(byte[] input) {
272 return new String(input, Charsets.UTF_8);
273 }
274
275 /**
Madan Jampani9eb55d12015-08-14 07:47:56 -0700276 * Returns a copy of the input byte array.
277 *
278 * @param original input
279 * @return copy of original
280 */
281 public static byte[] copyOf(byte[] original) {
282 return Arrays.copyOf(original, original.length);
283 }
284
285 /**
Thomas Vachuska6519e6f2015-03-11 02:29:31 -0700286 * Get property as a string value.
287 *
288 * @param properties properties to be looked up
289 * @param propertyName the name of the property to look up
290 * @return value when the propertyName is defined or return null
291 */
292 public static String get(Dictionary<?, ?> properties, String propertyName) {
293 Object v = properties.get(propertyName);
294 String s = (v instanceof String) ? (String) v :
295 v != null ? v.toString() : null;
296 return Strings.isNullOrEmpty(s) ? null : s.trim();
297 }
298
299 /**
Jian Lid9b5f552016-03-11 18:15:31 -0800300 * Get Integer property from the propertyName
301 * Return null if propertyName is not found.
302 *
303 * @param properties properties to be looked up
304 * @param propertyName the name of the property to look up
305 * @return value when the propertyName is defined or return null
306 */
307 public static Integer getIntegerProperty(Dictionary<?, ?> properties,
308 String propertyName) {
309 Integer value;
310 try {
311 String s = get(properties, propertyName);
312 value = Strings.isNullOrEmpty(s) ? null : Integer.valueOf(s);
313 } catch (NumberFormatException | ClassCastException e) {
314 value = null;
315 }
316 return value;
317 }
318
319 /**
320 * Get Integer property from the propertyName
321 * Return default value if propertyName is not found.
322 *
323 * @param properties properties to be looked up
324 * @param propertyName the name of the property to look up
325 * @param defaultValue the default value that to be assigned
326 * @return value when the propertyName is defined or return default value
327 */
328 public static int getIntegerProperty(Dictionary<?, ?> properties,
329 String propertyName,
330 int defaultValue) {
331 try {
332 String s = get(properties, propertyName);
333 return Strings.isNullOrEmpty(s) ? defaultValue : Integer.valueOf(s);
334 } catch (NumberFormatException | ClassCastException e) {
335 return defaultValue;
336 }
337 }
338
339 /**
340 * Check property name is defined and set to true.
341 *
342 * @param properties properties to be looked up
343 * @param propertyName the name of the property to look up
344 * @return value when the propertyName is defined or return null
345 */
346 public static Boolean isPropertyEnabled(Dictionary<?, ?> properties,
347 String propertyName) {
348 Boolean value;
349 try {
350 String s = get(properties, propertyName);
351 value = Strings.isNullOrEmpty(s) ? null : Boolean.valueOf(s);
352 } catch (ClassCastException e) {
353 value = null;
354 }
355 return value;
356 }
357
358 /**
359 * Check property name is defined as set to true.
360 *
361 * @param properties properties to be looked up
362 * @param propertyName the name of the property to look up
363 * @param defaultValue the default value that to be assigned
364 * @return value when the propertyName is defined or return the default value
365 */
366 public static boolean isPropertyEnabled(Dictionary<?, ?> properties,
367 String propertyName,
368 boolean defaultValue) {
369 try {
370 String s = get(properties, propertyName);
371 return Strings.isNullOrEmpty(s) ? defaultValue : Boolean.valueOf(s);
372 } catch (ClassCastException e) {
373 return defaultValue;
374 }
375 }
376
377 /**
tomf110fff2014-09-26 00:38:18 -0700378 * Suspends the current thread for a specified number of millis.
379 *
380 * @param ms number of millis
381 */
382 public static void delay(int ms) {
383 try {
384 Thread.sleep(ms);
385 } catch (InterruptedException e) {
386 throw new RuntimeException("Interrupted", e);
387 }
388 }
389
tom53efab52014-10-07 17:43:48 -0700390 /**
sdn94b00152016-08-30 02:12:32 -0700391 * Get Long property from the propertyName
392 * Return null if propertyName is not found.
393 *
394 * @param properties properties to be looked up
395 * @param propertyName the name of the property to look up
396 * @return value when the propertyName is defined or return null
397 */
398 public static Long getLongProperty(Dictionary<?, ?> properties,
399 String propertyName) {
400 Long value;
401 try {
402 String s = get(properties, propertyName);
403 value = Strings.isNullOrEmpty(s) ? null : Long.valueOf(s);
404 } catch (NumberFormatException | ClassCastException e) {
405 value = null;
406 }
407 return value;
408 }
409
410 /**
Madan Jampania29c6772015-08-17 13:17:07 -0700411 * Returns a function that retries execution on failure.
412 * @param base base function
413 * @param exceptionClass type of exception for which to retry
414 * @param maxRetries max number of retries before giving up
415 * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from
416 * the interval (0, maxDelayBetweenRetries]
417 * @return function
Thomas Vachuska87ae1d92015-08-19 17:39:11 -0700418 * @param <U> type of function input
419 * @param <V> type of function output
Madan Jampania29c6772015-08-17 13:17:07 -0700420 */
421 public static <U, V> Function<U, V> retryable(Function<U, V> base,
422 Class<? extends Throwable> exceptionClass,
423 int maxRetries,
424 int maxDelayBetweenRetries) {
425 return new RetryingFunction<>(base, exceptionClass, maxRetries, maxDelayBetweenRetries);
426 }
427
428 /**
429 * Returns a Supplier that retries execution on failure.
430 * @param base base supplier
431 * @param exceptionClass type of exception for which to retry
432 * @param maxRetries max number of retries before giving up
433 * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from
434 * the interval (0, maxDelayBetweenRetries]
435 * @return supplier
Thomas Vachuska87ae1d92015-08-19 17:39:11 -0700436 * @param <V> type of supplied result
Madan Jampania29c6772015-08-17 13:17:07 -0700437 */
438 public static <V> Supplier<V> retryable(Supplier<V> base,
439 Class<? extends Throwable> exceptionClass,
440 int maxRetries,
441 int maxDelayBetweenRetries) {
442 return () -> new RetryingFunction<>(v -> base.get(),
443 exceptionClass,
444 maxRetries,
445 maxDelayBetweenRetries).apply(null);
446 }
447
448 /**
Thomas Vachuskaadba1522015-06-04 15:08:30 -0700449 * Suspends the current thread for a random number of millis between 0 and
450 * the indicated limit.
451 *
452 * @param ms max number of millis
453 */
454 public static void randomDelay(int ms) {
455 try {
456 Thread.sleep(random.nextInt(ms));
457 } catch (InterruptedException e) {
458 throw new RuntimeException("Interrupted", e);
459 }
460 }
461
462 /**
Thomas Vachuskac40d4632015-04-09 16:55:03 -0700463 * Suspends the current thread for a specified number of millis and nanos.
464 *
465 * @param ms number of millis
466 * @param nanos number of nanos
467 */
468 public static void delay(int ms, int nanos) {
469 try {
470 Thread.sleep(ms, nanos);
471 } catch (InterruptedException e) {
472 throw new RuntimeException("Interrupted", e);
473 }
474 }
475
476 /**
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800477 * Purges the specified directory path.&nbsp;Use with great caution since
478 * no attempt is made to check for symbolic links, which could result in
479 * deletion of unintended files.
480 *
481 * @param path directory to be removed
482 * @throws java.io.IOException if unable to remove contents
483 */
484 public static void removeDirectory(String path) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800485 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700486 File dir = new File(path);
487 if (dir.exists() && dir.isDirectory()) {
488 walkFileTree(Paths.get(path), visitor);
489 if (visitor.exception != null) {
490 throw visitor.exception;
491 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800492 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800493 }
494
495 /**
496 * Purges the specified directory path.&nbsp;Use with great caution since
497 * no attempt is made to check for symbolic links, which could result in
498 * deletion of unintended files.
499 *
500 * @param dir directory to be removed
501 * @throws java.io.IOException if unable to remove contents
502 */
503 public static void removeDirectory(File dir) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800504 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700505 if (dir.exists() && dir.isDirectory()) {
506 walkFileTree(Paths.get(dir.getAbsolutePath()), visitor);
507 if (visitor.exception != null) {
508 throw visitor.exception;
509 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800510 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800511 }
512
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800513 // Auxiliary path visitor for recursive directory structure removal.
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800514 private static class DirectoryDeleter extends SimpleFileVisitor<Path> {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800515
516 private IOException exception;
517
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800518 @Override
519 public FileVisitResult visitFile(Path file, BasicFileAttributes attributes)
520 throws IOException {
521 if (attributes.isRegularFile()) {
522 delete(file);
523 }
524 return FileVisitResult.CONTINUE;
525 }
526
527 @Override
528 public FileVisitResult postVisitDirectory(Path directory, IOException ioe)
529 throws IOException {
530 delete(directory);
531 return FileVisitResult.CONTINUE;
532 }
533
534 @Override
535 public FileVisitResult visitFileFailed(Path file, IOException ioe)
536 throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800537 this.exception = ioe;
538 return FileVisitResult.TERMINATE;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800539 }
540 }
541
Madan Jampani30a57f82015-03-02 12:19:41 -0800542 /**
543 * Returns a human friendly time ago string for a specified system time.
Thomas Vachuska480adad2015-03-06 10:27:09 -0800544 *
Madan Jampani30a57f82015-03-02 12:19:41 -0800545 * @param unixTime system time in millis
546 * @return human friendly time ago
547 */
548 public static String timeAgo(long unixTime) {
549 long deltaMillis = System.currentTimeMillis() - unixTime;
550 long secondsSince = (long) (deltaMillis / 1000.0);
551 long minsSince = (long) (deltaMillis / (1000.0 * 60));
552 long hoursSince = (long) (deltaMillis / (1000.0 * 60 * 60));
553 long daysSince = (long) (deltaMillis / (1000.0 * 60 * 60 * 24));
554 if (daysSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800555 return String.format("%dd%dh ago", daysSince, hoursSince - daysSince * 24);
Madan Jampani30a57f82015-03-02 12:19:41 -0800556 } else if (hoursSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800557 return String.format("%dh%dm ago", hoursSince, minsSince - hoursSince * 60);
Madan Jampani30a57f82015-03-02 12:19:41 -0800558 } else if (minsSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800559 return String.format("%dm%ds ago", minsSince, secondsSince - minsSince * 60);
Madan Jampani30a57f82015-03-02 12:19:41 -0800560 } else if (secondsSince > 0) {
561 return String.format("%ds ago", secondsSince);
562 } else {
563 return "just now";
564 }
565 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800566
567 /**
568 * Copies the specified directory path.&nbsp;Use with great caution since
569 * no attempt is made to check for symbolic links, which could result in
570 * copy of unintended files.
571 *
572 * @param src directory to be copied
573 * @param dst destination directory to be removed
574 * @throws java.io.IOException if unable to remove contents
575 */
576 public static void copyDirectory(String src, String dst) throws IOException {
577 walkFileTree(Paths.get(src), new DirectoryCopier(src, dst));
578 }
579
580 /**
581 * Copies the specified directory path.&nbsp;Use with great caution since
582 * no attempt is made to check for symbolic links, which could result in
583 * copy of unintended files.
584 *
585 * @param src directory to be copied
586 * @param dst destination directory to be removed
587 * @throws java.io.IOException if unable to remove contents
588 */
589 public static void copyDirectory(File src, File dst) throws IOException {
590 walkFileTree(Paths.get(src.getAbsolutePath()),
591 new DirectoryCopier(src.getAbsolutePath(),
592 dst.getAbsolutePath()));
593 }
594
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700595 /**
596 * Returns the future value when complete or if future
597 * completes exceptionally returns the defaultValue.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700598 *
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700599 * @param future future
600 * @param defaultValue default value
601 * @param <T> future value type
602 * @return future value when complete or if future
603 * completes exceptionally returns the defaultValue.
604 */
605 public static <T> T futureGetOrElse(Future<T> future, T defaultValue) {
606 try {
607 return future.get();
608 } catch (InterruptedException e) {
609 Thread.currentThread().interrupt();
610 return defaultValue;
611 } catch (ExecutionException e) {
612 return defaultValue;
613 }
614 }
615
616 /**
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 timeout time to wait for successful completion
622 * @param timeUnit time unit
623 * @param defaultValue default value
624 * @param <T> future value type
625 * @return future value when complete or if future
626 * completes exceptionally returns the defaultValue.
627 */
628 public static <T> T futureGetOrElse(Future<T> future,
629 long timeout,
630 TimeUnit timeUnit,
631 T defaultValue) {
632 try {
633 return future.get(timeout, timeUnit);
634 } catch (InterruptedException e) {
635 Thread.currentThread().interrupt();
636 return defaultValue;
637 } catch (ExecutionException | TimeoutException e) {
638 return defaultValue;
639 }
640 }
641
Madan Jampani27b69c62015-05-15 15:49:02 -0700642 /**
643 * Returns a future that is completed exceptionally.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700644 *
Madan Jampani27b69c62015-05-15 15:49:02 -0700645 * @param t exception
646 * @param <T> future value type
647 * @return future
648 */
649 public static <T> CompletableFuture<T> exceptionalFuture(Throwable t) {
650 CompletableFuture<T> future = new CompletableFuture<>();
651 future.completeExceptionally(t);
652 return future;
653 }
654
655 /**
Jordan Halterman046faeb2017-05-01 15:10:13 -0700656 * Returns a future that's completed using the given {@code orderedExecutor} if the future is not blocked or the
657 * given {@code threadPoolExecutor} if the future is blocked.
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700658 * <p>
Jordan Halterman046faeb2017-05-01 15:10:13 -0700659 * This method allows futures to maintain single-thread semantics via the provided {@code orderedExecutor} while
660 * ensuring user code can block without blocking completion of futures. When the returned future or any of its
661 * descendants is blocked on a {@link CompletableFuture#get()} or {@link CompletableFuture#join()} call, completion
662 * of the returned future will be done using the provided {@code threadPoolExecutor}.
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700663 *
664 * @param future the future to convert into an asynchronous future
Jordan Halterman046faeb2017-05-01 15:10:13 -0700665 * @param orderedExecutor the ordered executor with which to attempt to complete the future
666 * @param threadPoolExecutor the backup executor with which to complete blocked futures
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700667 * @param <T> future value type
668 * @return a new completable future to be completed using the provided {@code executor} once the provided
669 * {@code future} is complete
670 */
Jordan Halterman046faeb2017-05-01 15:10:13 -0700671 public static <T> CompletableFuture<T> orderedFuture(
672 CompletableFuture<T> future,
673 Executor orderedExecutor,
674 Executor threadPoolExecutor) {
Jordan Haltermane265d372017-05-17 22:40:47 -0700675 if (future.isDone()) {
676 return future;
677 }
678
Jordan Halterman046faeb2017-05-01 15:10:13 -0700679 BlockingAwareFuture<T> newFuture = new BlockingAwareFuture<T>();
680 future.whenComplete((result, error) -> {
681 Runnable completer = () -> {
682 if (future.isCompletedExceptionally()) {
683 newFuture.completeExceptionally(error);
684 } else {
685 newFuture.complete(result);
686 }
687 };
688
689 if (newFuture.isBlocked()) {
690 threadPoolExecutor.execute(completer);
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700691 } else {
Jordan Halterman046faeb2017-05-01 15:10:13 -0700692 orderedExecutor.execute(completer);
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700693 }
Jordan Halterman046faeb2017-05-01 15:10:13 -0700694 });
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700695 return newFuture;
696 }
697
698 /**
Sho SHIMIZU85803e22016-01-13 21:53:43 -0800699 * Returns a new CompletableFuture completed with a list of computed values
700 * when all of the given CompletableFuture complete.
701 *
702 * @param futures the CompletableFutures
703 * @param <T> value type of CompletableFuture
704 * @return a new CompletableFuture that is completed when all of the given CompletableFutures complete
705 */
706 public static <T> CompletableFuture<List<T>> allOf(List<CompletableFuture<T>> futures) {
707 return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]))
708 .thenApply(v -> futures.stream()
709 .map(CompletableFuture::join)
710 .collect(Collectors.toList())
711 );
712 }
713
714 /**
Madan Jampani307a21e2016-09-01 15:49:47 -0700715 * Returns a new CompletableFuture completed by reducing a list of computed values
716 * when all of the given CompletableFuture complete.
717 *
718 * @param futures the CompletableFutures
719 * @param reducer reducer for computing the result
720 * @param emptyValue zero value to be returned if the input future list is empty
721 * @param <T> value type of CompletableFuture
722 * @return a new CompletableFuture that is completed when all of the given CompletableFutures complete
723 */
724 public static <T> CompletableFuture<T> allOf(List<CompletableFuture<T>> futures,
725 BinaryOperator<T> reducer,
726 T emptyValue) {
727 return Tools.allOf(futures)
728 .thenApply(resultList -> resultList.stream().reduce(reducer).orElse(emptyValue));
729 }
730
731 /**
732 * Returns a new CompletableFuture completed by with the first positive result from a list of
733 * input CompletableFutures.
734 *
735 * @param futures the input list of CompletableFutures
736 * @param positiveResultMatcher matcher to identify a positive result
737 * @param negativeResult value to complete with if none of the futures complete with a positive result
738 * @param <T> value type of CompletableFuture
739 * @return a new CompletableFuture
740 */
741 public static <T> CompletableFuture<T> firstOf(List<CompletableFuture<T>> futures,
742 Match<T> positiveResultMatcher,
743 T negativeResult) {
744 CompletableFuture<T> responseFuture = new CompletableFuture<>();
745 Tools.allOf(Lists.transform(futures, future -> future.thenAccept(r -> {
746 if (positiveResultMatcher.matches(r)) {
747 responseFuture.complete(r);
748 }
749 }))).whenComplete((r, e) -> {
750 if (!responseFuture.isDone()) {
751 if (e != null) {
752 responseFuture.completeExceptionally(e);
753 } else {
754 responseFuture.complete(negativeResult);
755 }
756 }
757 });
758 return responseFuture;
759 }
760
761 /**
Madan Jampani27b69c62015-05-15 15:49:02 -0700762 * Returns the contents of {@code ByteBuffer} as byte array.
763 * <p>
764 * WARNING: There is a performance cost due to array copy
765 * when using this method.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700766 *
Madan Jampani27b69c62015-05-15 15:49:02 -0700767 * @param buffer byte buffer
768 * @return byte array containing the byte buffer contents
769 */
770 public static byte[] byteBuffertoArray(ByteBuffer buffer) {
771 int length = buffer.remaining();
772 if (buffer.hasArray()) {
773 int offset = buffer.arrayOffset() + buffer.position();
774 return Arrays.copyOfRange(buffer.array(), offset, offset + length);
775 }
776 byte[] bytes = new byte[length];
777 buffer.duplicate().get(bytes);
778 return bytes;
779 }
780
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700781 /**
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700782 * Converts an iterable to a stream.
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700783 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700784 * @param it iterable to convert
785 * @param <T> type if item
786 * @return iterable as a stream
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700787 */
788 public static <T> Stream<T> stream(Iterable<T> it) {
789 return StreamSupport.stream(it.spliterator(), false);
790 }
791
Sho SHIMIZUb5638b82016-02-11 14:55:05 -0800792 /**
793 * Converts an optional to a stream.
794 *
795 * @param optional optional to convert
796 * @param <T> type of enclosed value
797 * @return optional as a stream
798 */
Sho SHIMIZU6ac20982016-05-04 09:50:54 -0700799 public static <T> Stream<T> stream(Optional<? extends T> optional) {
HIGUCHI Yuta0bc256f2016-05-06 15:28:26 -0700800 return optional.map(x -> Stream.<T>of(x)).orElse(Stream.empty());
Sho SHIMIZUb5638b82016-02-11 14:55:05 -0800801 }
802
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800803 // Auxiliary path visitor for recursive directory structure copying.
804 private static class DirectoryCopier extends SimpleFileVisitor<Path> {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800805 private Path src;
806 private Path dst;
807 private StandardCopyOption copyOption = StandardCopyOption.REPLACE_EXISTING;
808
809 DirectoryCopier(String src, String dst) {
810 this.src = Paths.get(src);
811 this.dst = Paths.get(dst);
812 }
813
814 @Override
815 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
816 Path targetPath = dst.resolve(src.relativize(dir));
817 if (!Files.exists(targetPath)) {
818 Files.createDirectory(targetPath);
819 }
820 return FileVisitResult.CONTINUE;
821 }
822
823 @Override
824 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
825 Files.copy(file, dst.resolve(src.relativize(file)), copyOption);
826 return FileVisitResult.CONTINUE;
827 }
828 }
829
tom5f38b3a2014-08-27 23:50:54 -0700830}