blob: d2aa1f50be90c0b6fce0531d101134b4bbad1171 [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
Madan Jampani307a21e2016-09-01 15:49:47 -070018import static java.nio.file.Files.delete;
19import static java.nio.file.Files.walkFileTree;
20import static org.onlab.util.GroupedThreadFactory.groupedThreadFactory;
21import static org.slf4j.LoggerFactory.getLogger;
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080022
tom53efab52014-10-07 17:43:48 -070023import java.io.File;
tom53efab52014-10-07 17:43:48 -070024import java.io.IOException;
Madan Jampani27b69c62015-05-15 15:49:02 -070025import java.nio.ByteBuffer;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080026import java.nio.file.FileVisitResult;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080027import java.nio.file.Files;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080028import java.nio.file.Path;
29import java.nio.file.Paths;
30import java.nio.file.SimpleFileVisitor;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080031import java.nio.file.StandardCopyOption;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080032import java.nio.file.attribute.BasicFileAttributes;
Madan Jampani27b69c62015-05-15 15:49:02 -070033import java.util.Arrays;
Brian O'Connore2eac102015-02-12 18:30:22 -080034import java.util.Collection;
Thomas Vachuska6519e6f2015-03-11 02:29:31 -070035import java.util.Dictionary;
tom53efab52014-10-07 17:43:48 -070036import java.util.List;
Sho SHIMIZUb5638b82016-02-11 14:55:05 -080037import java.util.Optional;
Thomas Vachuskaadba1522015-06-04 15:08:30 -070038import java.util.Random;
Ray Milkey36992c82015-11-17 13:31:15 -080039import java.util.Set;
Madan Jampani27b69c62015-05-15 15:49:02 -070040import java.util.concurrent.CompletableFuture;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070041import java.util.concurrent.ExecutionException;
Jordan Halterman9bdc24f2017-04-19 23:45:12 -070042import java.util.concurrent.Executor;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070043import java.util.concurrent.Future;
tom5f38b3a2014-08-27 23:50:54 -070044import java.util.concurrent.ThreadFactory;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070045import java.util.concurrent.TimeUnit;
46import java.util.concurrent.TimeoutException;
Madan Jampani307a21e2016-09-01 15:49:47 -070047import java.util.function.BinaryOperator;
Madan Jampania29c6772015-08-17 13:17:07 -070048import java.util.function.Function;
49import java.util.function.Supplier;
Sho SHIMIZU85803e22016-01-13 21:53:43 -080050import java.util.stream.Collectors;
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -070051import java.util.stream.Stream;
52import java.util.stream.StreamSupport;
tom5f38b3a2014-08-27 23:50:54 -070053
Madan Jampani307a21e2016-09-01 15:49:47 -070054import org.slf4j.Logger;
55
56import com.google.common.base.Charsets;
57import com.google.common.base.Strings;
58import com.google.common.collect.Lists;
59import com.google.common.primitives.UnsignedLongs;
60import com.google.common.util.concurrent.ThreadFactoryBuilder;
Ray Milkey705d9bc2014-11-18 08:19:00 -080061
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080062/**
63 * Miscellaneous utility methods.
64 */
tom5f38b3a2014-08-27 23:50:54 -070065public abstract class Tools {
66
67 private Tools() {
68 }
69
Thomas Vachuska02aeb032015-01-06 22:36:30 -080070 private static final Logger log = getLogger(Tools.class);
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080071
Thomas Vachuskaadba1522015-06-04 15:08:30 -070072 private static Random random = new Random();
73
tom5f38b3a2014-08-27 23:50:54 -070074 /**
75 * Returns a thread factory that produces threads named according to the
76 * supplied name pattern.
77 *
78 * @param pattern name pattern
79 * @return thread factory
80 */
81 public static ThreadFactory namedThreads(String pattern) {
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080082 return new ThreadFactoryBuilder()
83 .setNameFormat(pattern)
Thomas Vachuska480adad2015-03-06 10:27:09 -080084 .setUncaughtExceptionHandler((t, e) -> log.error("Uncaught exception on " + t.getName(), e))
85 .build();
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080086 }
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080087
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080088 /**
89 * Returns a thread factory that produces threads named according to the
90 * supplied name pattern and from the specified thread-group. The thread
91 * group name is expected to be specified in slash-delimited format, e.g.
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080092 * {@code onos/intent}. The thread names will be produced by converting
93 * the thread group name into dash-delimited format and pre-pended to the
94 * specified pattern.
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080095 *
96 * @param groupName group name in slash-delimited format to indicate hierarchy
97 * @param pattern name pattern
98 * @return thread factory
99 */
100 public static ThreadFactory groupedThreads(String groupName, String pattern) {
Jian Li03e9fb02016-03-01 17:13:54 -0800101 return groupedThreads(groupName, pattern, log);
102 }
103
104 /**
105 * Returns a thread factory that produces threads named according to the
106 * supplied name pattern and from the specified thread-group. The thread
107 * group name is expected to be specified in slash-delimited format, e.g.
108 * {@code onos/intent}. The thread names will be produced by converting
109 * the thread group name into dash-delimited format and pre-pended to the
110 * specified pattern. If a logger is specified, it will use the logger to
111 * print out the exception if it has any.
112 *
113 * @param groupName group name in slash-delimited format to indicate hierarchy
114 * @param pattern name pattern
115 * @param logger logger
116 * @return thread factory
117 */
118 public static ThreadFactory groupedThreads(String groupName, String pattern, Logger logger) {
119 if (logger == null) {
120 return groupedThreads(groupName, pattern);
121 }
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -0800122 return new ThreadFactoryBuilder()
123 .setThreadFactory(groupedThreadFactory(groupName))
Thomas Vachuskac13b90a2015-02-18 18:19:55 -0800124 .setNameFormat(groupName.replace(GroupedThreadFactory.DELIMITER, "-") + "-" + pattern)
Jian Li03e9fb02016-03-01 17:13:54 -0800125 .setUncaughtExceptionHandler((t, e) -> logger.error("Uncaught exception on " + t.getName(), e))
Thomas Vachuska480adad2015-03-06 10:27:09 -0800126 .build();
tom5f38b3a2014-08-27 23:50:54 -0700127 }
128
tom782a7cf2014-09-11 23:58:38 -0700129 /**
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800130 * Returns a thread factory that produces threads with MIN_PRIORITY.
131 *
132 * @param factory backing ThreadFactory
133 * @return thread factory
134 */
135 public static ThreadFactory minPriority(ThreadFactory factory) {
136 return new ThreadFactoryBuilder()
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800137 .setThreadFactory(factory)
138 .setPriority(Thread.MIN_PRIORITY)
139 .build();
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800140 }
141
142 /**
Yuta HIGUCHIa2a11cd2016-12-19 14:19:11 -0800143 * Returns a thread factory that produces threads with MAX_PRIORITY.
144 *
145 * @param factory backing ThreadFactory
146 * @return thread factory
147 */
148 public static ThreadFactory maxPriority(ThreadFactory factory) {
149 return new ThreadFactoryBuilder()
150 .setThreadFactory(factory)
151 .setPriority(Thread.MAX_PRIORITY)
152 .build();
153 }
154
155 /**
Brian O'Connore2eac102015-02-12 18:30:22 -0800156 * Returns true if the collection is null or is empty.
157 *
158 * @param collection collection to test
159 * @return true if null or empty; false otherwise
160 */
161 public static boolean isNullOrEmpty(Collection collection) {
162 return collection == null || collection.isEmpty();
163 }
164
165 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700166 * Returns the specified item if that item is not null; otherwise throws
Thomas Vachuskaca88bb72015-04-08 19:38:02 -0700167 * not found exception.
168 *
169 * @param item item to check
170 * @param message not found message
171 * @param <T> item type
172 * @return item if not null
173 * @throws org.onlab.util.ItemNotFoundException if item is null
174 */
175 public static <T> T nullIsNotFound(T item, String message) {
176 if (item == null) {
177 throw new ItemNotFoundException(message);
178 }
179 return item;
180 }
181
182 /**
Ray Milkey36992c82015-11-17 13:31:15 -0800183 * Returns the specified set if the set is not null and not empty;
184 * otherwise throws a not found exception.
185 *
186 * @param item set to check
187 * @param message not found message
188 * @param <T> Set item type
189 * @return item if not null and not empty
190 * @throws org.onlab.util.ItemNotFoundException if set is null or empty
191 */
192 public static <T> Set<T> emptyIsNotFound(Set<T> item, String message) {
193 if (item == null || item.isEmpty()) {
194 throw new ItemNotFoundException(message);
195 }
196 return item;
197 }
198
199 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700200 * Returns the specified item if that item is not null; otherwise throws
201 * bad argument exception.
202 *
203 * @param item item to check
204 * @param message not found message
205 * @param <T> item type
206 * @return item if not null
207 * @throws IllegalArgumentException if item is null
208 */
209 public static <T> T nullIsIllegal(T item, String message) {
210 if (item == null) {
211 throw new IllegalArgumentException(message);
212 }
213 return item;
214 }
215
216 /**
tom782a7cf2014-09-11 23:58:38 -0700217 * Converts a string from hex to long.
218 *
219 * @param string hex number in string form; sans 0x
220 * @return long value
221 */
222 public static long fromHex(String string) {
223 return UnsignedLongs.parseUnsignedLong(string, 16);
224 }
225
226 /**
227 * Converts a long value to hex string; 16 wide and sans 0x.
228 *
229 * @param value long value
230 * @return hex string
231 */
232 public static String toHex(long value) {
233 return Strings.padStart(UnsignedLongs.toString(value, 16), 16, '0');
234 }
235
236 /**
237 * Converts a long value to hex string; 16 wide and sans 0x.
238 *
239 * @param value long value
240 * @param width string width; zero padded
241 * @return hex string
242 */
243 public static String toHex(long value, int width) {
244 return Strings.padStart(UnsignedLongs.toString(value, 16), width, '0');
245 }
tomf110fff2014-09-26 00:38:18 -0700246
247 /**
Madan Jampanif2f086c2016-01-13 16:15:39 -0800248 * Returns the UTF-8 encoded byte[] representation of a String.
Jian Lidfba7392016-01-22 16:46:58 -0800249 * @param input input string
250 * @return UTF-8 encoded byte array
Madan Jampanif2f086c2016-01-13 16:15:39 -0800251 */
252 public static byte[] getBytesUtf8(String input) {
253 return input.getBytes(Charsets.UTF_8);
254 }
255
256 /**
257 * Returns the String representation of UTF-8 encoded byte[].
Jian Lidfba7392016-01-22 16:46:58 -0800258 * @param input input byte array
259 * @return UTF-8 encoded string
Madan Jampanif2f086c2016-01-13 16:15:39 -0800260 */
261 public static String toStringUtf8(byte[] input) {
262 return new String(input, Charsets.UTF_8);
263 }
264
265 /**
Madan Jampani9eb55d12015-08-14 07:47:56 -0700266 * Returns a copy of the input byte array.
267 *
268 * @param original input
269 * @return copy of original
270 */
271 public static byte[] copyOf(byte[] original) {
272 return Arrays.copyOf(original, original.length);
273 }
274
275 /**
Thomas Vachuska6519e6f2015-03-11 02:29:31 -0700276 * Get property as a string value.
277 *
278 * @param properties properties to be looked up
279 * @param propertyName the name of the property to look up
280 * @return value when the propertyName is defined or return null
281 */
282 public static String get(Dictionary<?, ?> properties, String propertyName) {
283 Object v = properties.get(propertyName);
284 String s = (v instanceof String) ? (String) v :
285 v != null ? v.toString() : null;
286 return Strings.isNullOrEmpty(s) ? null : s.trim();
287 }
288
289 /**
Jian Lid9b5f552016-03-11 18:15:31 -0800290 * Get Integer property from the propertyName
291 * Return null if propertyName is not found.
292 *
293 * @param properties properties to be looked up
294 * @param propertyName the name of the property to look up
295 * @return value when the propertyName is defined or return null
296 */
297 public static Integer getIntegerProperty(Dictionary<?, ?> properties,
298 String propertyName) {
299 Integer value;
300 try {
301 String s = get(properties, propertyName);
302 value = Strings.isNullOrEmpty(s) ? null : Integer.valueOf(s);
303 } catch (NumberFormatException | ClassCastException e) {
304 value = null;
305 }
306 return value;
307 }
308
309 /**
310 * Get Integer property from the propertyName
311 * Return default value if propertyName is not found.
312 *
313 * @param properties properties to be looked up
314 * @param propertyName the name of the property to look up
315 * @param defaultValue the default value that to be assigned
316 * @return value when the propertyName is defined or return default value
317 */
318 public static int getIntegerProperty(Dictionary<?, ?> properties,
319 String propertyName,
320 int defaultValue) {
321 try {
322 String s = get(properties, propertyName);
323 return Strings.isNullOrEmpty(s) ? defaultValue : Integer.valueOf(s);
324 } catch (NumberFormatException | ClassCastException e) {
325 return defaultValue;
326 }
327 }
328
329 /**
330 * Check property name is defined and set to true.
331 *
332 * @param properties properties to be looked up
333 * @param propertyName the name of the property to look up
334 * @return value when the propertyName is defined or return null
335 */
336 public static Boolean isPropertyEnabled(Dictionary<?, ?> properties,
337 String propertyName) {
338 Boolean value;
339 try {
340 String s = get(properties, propertyName);
341 value = Strings.isNullOrEmpty(s) ? null : Boolean.valueOf(s);
342 } catch (ClassCastException e) {
343 value = null;
344 }
345 return value;
346 }
347
348 /**
349 * Check property name is defined as set to true.
350 *
351 * @param properties properties to be looked up
352 * @param propertyName the name of the property to look up
353 * @param defaultValue the default value that to be assigned
354 * @return value when the propertyName is defined or return the default value
355 */
356 public static boolean isPropertyEnabled(Dictionary<?, ?> properties,
357 String propertyName,
358 boolean defaultValue) {
359 try {
360 String s = get(properties, propertyName);
361 return Strings.isNullOrEmpty(s) ? defaultValue : Boolean.valueOf(s);
362 } catch (ClassCastException e) {
363 return defaultValue;
364 }
365 }
366
367 /**
tomf110fff2014-09-26 00:38:18 -0700368 * Suspends the current thread for a specified number of millis.
369 *
370 * @param ms number of millis
371 */
372 public static void delay(int ms) {
373 try {
374 Thread.sleep(ms);
375 } catch (InterruptedException e) {
376 throw new RuntimeException("Interrupted", e);
377 }
378 }
379
tom53efab52014-10-07 17:43:48 -0700380 /**
sdn94b00152016-08-30 02:12:32 -0700381 * Get Long property from the propertyName
382 * Return null if propertyName is not found.
383 *
384 * @param properties properties to be looked up
385 * @param propertyName the name of the property to look up
386 * @return value when the propertyName is defined or return null
387 */
388 public static Long getLongProperty(Dictionary<?, ?> properties,
389 String propertyName) {
390 Long value;
391 try {
392 String s = get(properties, propertyName);
393 value = Strings.isNullOrEmpty(s) ? null : Long.valueOf(s);
394 } catch (NumberFormatException | ClassCastException e) {
395 value = null;
396 }
397 return value;
398 }
399
400 /**
Madan Jampania29c6772015-08-17 13:17:07 -0700401 * Returns a function that retries execution on failure.
402 * @param base base function
403 * @param exceptionClass type of exception for which to retry
404 * @param maxRetries max number of retries before giving up
405 * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from
406 * the interval (0, maxDelayBetweenRetries]
407 * @return function
Thomas Vachuska87ae1d92015-08-19 17:39:11 -0700408 * @param <U> type of function input
409 * @param <V> type of function output
Madan Jampania29c6772015-08-17 13:17:07 -0700410 */
411 public static <U, V> Function<U, V> retryable(Function<U, V> base,
412 Class<? extends Throwable> exceptionClass,
413 int maxRetries,
414 int maxDelayBetweenRetries) {
415 return new RetryingFunction<>(base, exceptionClass, maxRetries, maxDelayBetweenRetries);
416 }
417
418 /**
419 * Returns a Supplier that retries execution on failure.
420 * @param base base supplier
421 * @param exceptionClass type of exception for which to retry
422 * @param maxRetries max number of retries before giving up
423 * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from
424 * the interval (0, maxDelayBetweenRetries]
425 * @return supplier
Thomas Vachuska87ae1d92015-08-19 17:39:11 -0700426 * @param <V> type of supplied result
Madan Jampania29c6772015-08-17 13:17:07 -0700427 */
428 public static <V> Supplier<V> retryable(Supplier<V> base,
429 Class<? extends Throwable> exceptionClass,
430 int maxRetries,
431 int maxDelayBetweenRetries) {
432 return () -> new RetryingFunction<>(v -> base.get(),
433 exceptionClass,
434 maxRetries,
435 maxDelayBetweenRetries).apply(null);
436 }
437
438 /**
Thomas Vachuskaadba1522015-06-04 15:08:30 -0700439 * Suspends the current thread for a random number of millis between 0 and
440 * the indicated limit.
441 *
442 * @param ms max number of millis
443 */
444 public static void randomDelay(int ms) {
445 try {
446 Thread.sleep(random.nextInt(ms));
447 } catch (InterruptedException e) {
448 throw new RuntimeException("Interrupted", e);
449 }
450 }
451
452 /**
Thomas Vachuskac40d4632015-04-09 16:55:03 -0700453 * Suspends the current thread for a specified number of millis and nanos.
454 *
455 * @param ms number of millis
456 * @param nanos number of nanos
457 */
458 public static void delay(int ms, int nanos) {
459 try {
460 Thread.sleep(ms, nanos);
461 } catch (InterruptedException e) {
462 throw new RuntimeException("Interrupted", e);
463 }
464 }
465
466 /**
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800467 * Purges the specified directory path.&nbsp;Use with great caution since
468 * no attempt is made to check for symbolic links, which could result in
469 * deletion of unintended files.
470 *
471 * @param path directory to be removed
472 * @throws java.io.IOException if unable to remove contents
473 */
474 public static void removeDirectory(String path) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800475 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700476 File dir = new File(path);
477 if (dir.exists() && dir.isDirectory()) {
478 walkFileTree(Paths.get(path), visitor);
479 if (visitor.exception != null) {
480 throw visitor.exception;
481 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800482 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800483 }
484
485 /**
486 * Purges the specified directory path.&nbsp;Use with great caution since
487 * no attempt is made to check for symbolic links, which could result in
488 * deletion of unintended files.
489 *
490 * @param dir directory to be removed
491 * @throws java.io.IOException if unable to remove contents
492 */
493 public static void removeDirectory(File dir) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800494 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700495 if (dir.exists() && dir.isDirectory()) {
496 walkFileTree(Paths.get(dir.getAbsolutePath()), visitor);
497 if (visitor.exception != null) {
498 throw visitor.exception;
499 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800500 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800501 }
502
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800503 // Auxiliary path visitor for recursive directory structure removal.
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800504 private static class DirectoryDeleter extends SimpleFileVisitor<Path> {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800505
506 private IOException exception;
507
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800508 @Override
509 public FileVisitResult visitFile(Path file, BasicFileAttributes attributes)
510 throws IOException {
511 if (attributes.isRegularFile()) {
512 delete(file);
513 }
514 return FileVisitResult.CONTINUE;
515 }
516
517 @Override
518 public FileVisitResult postVisitDirectory(Path directory, IOException ioe)
519 throws IOException {
520 delete(directory);
521 return FileVisitResult.CONTINUE;
522 }
523
524 @Override
525 public FileVisitResult visitFileFailed(Path file, IOException ioe)
526 throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800527 this.exception = ioe;
528 return FileVisitResult.TERMINATE;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800529 }
530 }
531
Madan Jampani30a57f82015-03-02 12:19:41 -0800532 /**
533 * Returns a human friendly time ago string for a specified system time.
Thomas Vachuska480adad2015-03-06 10:27:09 -0800534 *
Madan Jampani30a57f82015-03-02 12:19:41 -0800535 * @param unixTime system time in millis
536 * @return human friendly time ago
537 */
538 public static String timeAgo(long unixTime) {
539 long deltaMillis = System.currentTimeMillis() - unixTime;
540 long secondsSince = (long) (deltaMillis / 1000.0);
541 long minsSince = (long) (deltaMillis / (1000.0 * 60));
542 long hoursSince = (long) (deltaMillis / (1000.0 * 60 * 60));
543 long daysSince = (long) (deltaMillis / (1000.0 * 60 * 60 * 24));
544 if (daysSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800545 return String.format("%dd%dh ago", daysSince, hoursSince - daysSince * 24);
Madan Jampani30a57f82015-03-02 12:19:41 -0800546 } else if (hoursSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800547 return String.format("%dh%dm ago", hoursSince, minsSince - hoursSince * 60);
Madan Jampani30a57f82015-03-02 12:19:41 -0800548 } else if (minsSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800549 return String.format("%dm%ds ago", minsSince, secondsSince - minsSince * 60);
Madan Jampani30a57f82015-03-02 12:19:41 -0800550 } else if (secondsSince > 0) {
551 return String.format("%ds ago", secondsSince);
552 } else {
553 return "just now";
554 }
555 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800556
557 /**
558 * Copies the specified directory path.&nbsp;Use with great caution since
559 * no attempt is made to check for symbolic links, which could result in
560 * copy of unintended files.
561 *
562 * @param src directory to be copied
563 * @param dst destination directory to be removed
564 * @throws java.io.IOException if unable to remove contents
565 */
566 public static void copyDirectory(String src, String dst) throws IOException {
567 walkFileTree(Paths.get(src), new DirectoryCopier(src, dst));
568 }
569
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(File src, File dst) throws IOException {
580 walkFileTree(Paths.get(src.getAbsolutePath()),
581 new DirectoryCopier(src.getAbsolutePath(),
582 dst.getAbsolutePath()));
583 }
584
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700585 /**
586 * Returns the future value when complete or if future
587 * completes exceptionally returns the defaultValue.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700588 *
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700589 * @param future future
590 * @param defaultValue default value
591 * @param <T> future value type
592 * @return future value when complete or if future
593 * completes exceptionally returns the defaultValue.
594 */
595 public static <T> T futureGetOrElse(Future<T> future, T defaultValue) {
596 try {
597 return future.get();
598 } catch (InterruptedException e) {
599 Thread.currentThread().interrupt();
600 return defaultValue;
601 } catch (ExecutionException e) {
602 return defaultValue;
603 }
604 }
605
606 /**
607 * Returns the future value when complete or if future
608 * completes exceptionally returns the defaultValue.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700609 *
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700610 * @param future future
611 * @param timeout time to wait for successful completion
612 * @param timeUnit time unit
613 * @param defaultValue default value
614 * @param <T> future value type
615 * @return future value when complete or if future
616 * completes exceptionally returns the defaultValue.
617 */
618 public static <T> T futureGetOrElse(Future<T> future,
619 long timeout,
620 TimeUnit timeUnit,
621 T defaultValue) {
622 try {
623 return future.get(timeout, timeUnit);
624 } catch (InterruptedException e) {
625 Thread.currentThread().interrupt();
626 return defaultValue;
627 } catch (ExecutionException | TimeoutException e) {
628 return defaultValue;
629 }
630 }
631
Madan Jampani27b69c62015-05-15 15:49:02 -0700632 /**
633 * Returns a future that is completed exceptionally.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700634 *
Madan Jampani27b69c62015-05-15 15:49:02 -0700635 * @param t exception
636 * @param <T> future value type
637 * @return future
638 */
639 public static <T> CompletableFuture<T> exceptionalFuture(Throwable t) {
640 CompletableFuture<T> future = new CompletableFuture<>();
641 future.completeExceptionally(t);
642 return future;
643 }
644
645 /**
Jordan Halterman046faeb2017-05-01 15:10:13 -0700646 * Returns a future that's completed using the given {@code orderedExecutor} if the future is not blocked or the
647 * given {@code threadPoolExecutor} if the future is blocked.
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700648 * <p>
Jordan Halterman046faeb2017-05-01 15:10:13 -0700649 * This method allows futures to maintain single-thread semantics via the provided {@code orderedExecutor} while
650 * ensuring user code can block without blocking completion of futures. When the returned future or any of its
651 * descendants is blocked on a {@link CompletableFuture#get()} or {@link CompletableFuture#join()} call, completion
652 * of the returned future will be done using the provided {@code threadPoolExecutor}.
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700653 *
654 * @param future the future to convert into an asynchronous future
Jordan Halterman046faeb2017-05-01 15:10:13 -0700655 * @param orderedExecutor the ordered executor with which to attempt to complete the future
656 * @param threadPoolExecutor the backup executor with which to complete blocked futures
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700657 * @param <T> future value type
658 * @return a new completable future to be completed using the provided {@code executor} once the provided
659 * {@code future} is complete
660 */
Jordan Halterman046faeb2017-05-01 15:10:13 -0700661 public static <T> CompletableFuture<T> orderedFuture(
662 CompletableFuture<T> future,
663 Executor orderedExecutor,
664 Executor threadPoolExecutor) {
Jordan Haltermane265d372017-05-17 22:40:47 -0700665 if (future.isDone()) {
666 return future;
667 }
668
Jordan Halterman046faeb2017-05-01 15:10:13 -0700669 BlockingAwareFuture<T> newFuture = new BlockingAwareFuture<T>();
670 future.whenComplete((result, error) -> {
671 Runnable completer = () -> {
672 if (future.isCompletedExceptionally()) {
673 newFuture.completeExceptionally(error);
674 } else {
675 newFuture.complete(result);
676 }
677 };
678
679 if (newFuture.isBlocked()) {
680 threadPoolExecutor.execute(completer);
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700681 } else {
Jordan Halterman046faeb2017-05-01 15:10:13 -0700682 orderedExecutor.execute(completer);
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700683 }
Jordan Halterman046faeb2017-05-01 15:10:13 -0700684 });
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700685 return newFuture;
686 }
687
688 /**
Sho SHIMIZU85803e22016-01-13 21:53:43 -0800689 * Returns a new CompletableFuture completed with a list of computed values
690 * when all of the given CompletableFuture complete.
691 *
692 * @param futures the CompletableFutures
693 * @param <T> value type of CompletableFuture
694 * @return a new CompletableFuture that is completed when all of the given CompletableFutures complete
695 */
696 public static <T> CompletableFuture<List<T>> allOf(List<CompletableFuture<T>> futures) {
697 return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]))
698 .thenApply(v -> futures.stream()
699 .map(CompletableFuture::join)
700 .collect(Collectors.toList())
701 );
702 }
703
704 /**
Madan Jampani307a21e2016-09-01 15:49:47 -0700705 * Returns a new CompletableFuture completed by reducing a list of computed values
706 * when all of the given CompletableFuture complete.
707 *
708 * @param futures the CompletableFutures
709 * @param reducer reducer for computing the result
710 * @param emptyValue zero value to be returned if the input future list is empty
711 * @param <T> value type of CompletableFuture
712 * @return a new CompletableFuture that is completed when all of the given CompletableFutures complete
713 */
714 public static <T> CompletableFuture<T> allOf(List<CompletableFuture<T>> futures,
715 BinaryOperator<T> reducer,
716 T emptyValue) {
717 return Tools.allOf(futures)
718 .thenApply(resultList -> resultList.stream().reduce(reducer).orElse(emptyValue));
719 }
720
721 /**
722 * Returns a new CompletableFuture completed by with the first positive result from a list of
723 * input CompletableFutures.
724 *
725 * @param futures the input list of CompletableFutures
726 * @param positiveResultMatcher matcher to identify a positive result
727 * @param negativeResult value to complete with if none of the futures complete with a positive result
728 * @param <T> value type of CompletableFuture
729 * @return a new CompletableFuture
730 */
731 public static <T> CompletableFuture<T> firstOf(List<CompletableFuture<T>> futures,
732 Match<T> positiveResultMatcher,
733 T negativeResult) {
734 CompletableFuture<T> responseFuture = new CompletableFuture<>();
735 Tools.allOf(Lists.transform(futures, future -> future.thenAccept(r -> {
736 if (positiveResultMatcher.matches(r)) {
737 responseFuture.complete(r);
738 }
739 }))).whenComplete((r, e) -> {
740 if (!responseFuture.isDone()) {
741 if (e != null) {
742 responseFuture.completeExceptionally(e);
743 } else {
744 responseFuture.complete(negativeResult);
745 }
746 }
747 });
748 return responseFuture;
749 }
750
751 /**
Madan Jampani27b69c62015-05-15 15:49:02 -0700752 * Returns the contents of {@code ByteBuffer} as byte array.
753 * <p>
754 * WARNING: There is a performance cost due to array copy
755 * when using this method.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700756 *
Madan Jampani27b69c62015-05-15 15:49:02 -0700757 * @param buffer byte buffer
758 * @return byte array containing the byte buffer contents
759 */
760 public static byte[] byteBuffertoArray(ByteBuffer buffer) {
761 int length = buffer.remaining();
762 if (buffer.hasArray()) {
763 int offset = buffer.arrayOffset() + buffer.position();
764 return Arrays.copyOfRange(buffer.array(), offset, offset + length);
765 }
766 byte[] bytes = new byte[length];
767 buffer.duplicate().get(bytes);
768 return bytes;
769 }
770
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700771 /**
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700772 * Converts an iterable to a stream.
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700773 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700774 * @param it iterable to convert
775 * @param <T> type if item
776 * @return iterable as a stream
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700777 */
778 public static <T> Stream<T> stream(Iterable<T> it) {
779 return StreamSupport.stream(it.spliterator(), false);
780 }
781
Sho SHIMIZUb5638b82016-02-11 14:55:05 -0800782 /**
783 * Converts an optional to a stream.
784 *
785 * @param optional optional to convert
786 * @param <T> type of enclosed value
787 * @return optional as a stream
788 */
Sho SHIMIZU6ac20982016-05-04 09:50:54 -0700789 public static <T> Stream<T> stream(Optional<? extends T> optional) {
HIGUCHI Yuta0bc256f2016-05-06 15:28:26 -0700790 return optional.map(x -> Stream.<T>of(x)).orElse(Stream.empty());
Sho SHIMIZUb5638b82016-02-11 14:55:05 -0800791 }
792
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800793 // Auxiliary path visitor for recursive directory structure copying.
794 private static class DirectoryCopier extends SimpleFileVisitor<Path> {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800795 private Path src;
796 private Path dst;
797 private StandardCopyOption copyOption = StandardCopyOption.REPLACE_EXISTING;
798
799 DirectoryCopier(String src, String dst) {
800 this.src = Paths.get(src);
801 this.dst = Paths.get(dst);
802 }
803
804 @Override
805 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
806 Path targetPath = dst.resolve(src.relativize(dir));
807 if (!Files.exists(targetPath)) {
808 Files.createDirectory(targetPath);
809 }
810 return FileVisitResult.CONTINUE;
811 }
812
813 @Override
814 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
815 Files.copy(file, dst.resolve(src.relativize(file)), copyOption);
816 return FileVisitResult.CONTINUE;
817 }
818 }
819
tom5f38b3a2014-08-27 23:50:54 -0700820}