blob: f82b6e5f416ce96b5921815f916eae4e8b96f4a1 [file] [log] [blame]
Thomas Vachuska24c849c2014-10-27 09:53:05 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2014-present Open Networking Foundation
Thomas Vachuska24c849c2014-10-27 09:53:05 -07003 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07004 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
Thomas Vachuska24c849c2014-10-27 09:53:05 -07007 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07008 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
Thomas Vachuska24c849c2014-10-27 09:53:05 -070015 */
tom5f38b3a2014-08-27 23:50:54 -070016package org.onlab.util;
17
Ray Milkeyb784adb2018-04-02 15:33:07 -070018import com.fasterxml.jackson.databind.ObjectMapper;
19import com.fasterxml.jackson.databind.node.ObjectNode;
Jonathan Hartcc962d82016-08-09 16:52:22 -070020import com.google.common.base.Charsets;
21import com.google.common.base.Strings;
22import com.google.common.collect.Lists;
23import com.google.common.primitives.UnsignedLongs;
24import com.google.common.util.concurrent.ThreadFactoryBuilder;
25import org.slf4j.Logger;
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080026
tom53efab52014-10-07 17:43:48 -070027import java.io.File;
tom53efab52014-10-07 17:43:48 -070028import java.io.IOException;
Ray Milkeyb784adb2018-04-02 15:33:07 -070029import java.io.InputStream;
Madan Jampani27b69c62015-05-15 15:49:02 -070030import java.nio.ByteBuffer;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080031import java.nio.file.FileVisitResult;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080032import java.nio.file.Files;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080033import java.nio.file.Path;
34import java.nio.file.Paths;
35import java.nio.file.SimpleFileVisitor;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080036import java.nio.file.StandardCopyOption;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080037import java.nio.file.attribute.BasicFileAttributes;
Madan Jampani27b69c62015-05-15 15:49:02 -070038import java.util.Arrays;
Brian O'Connore2eac102015-02-12 18:30:22 -080039import java.util.Collection;
Thomas Vachuska6519e6f2015-03-11 02:29:31 -070040import java.util.Dictionary;
tom53efab52014-10-07 17:43:48 -070041import java.util.List;
Sho SHIMIZUb5638b82016-02-11 14:55:05 -080042import java.util.Optional;
Thomas Vachuskaadba1522015-06-04 15:08:30 -070043import java.util.Random;
Ray Milkey36992c82015-11-17 13:31:15 -080044import java.util.Set;
Madan Jampani27b69c62015-05-15 15:49:02 -070045import java.util.concurrent.CompletableFuture;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070046import java.util.concurrent.ExecutionException;
Jordan Halterman9bdc24f2017-04-19 23:45:12 -070047import java.util.concurrent.Executor;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070048import java.util.concurrent.Future;
tom5f38b3a2014-08-27 23:50:54 -070049import java.util.concurrent.ThreadFactory;
Madan Jampani2bfa94c2015-04-11 05:03:49 -070050import java.util.concurrent.TimeUnit;
51import java.util.concurrent.TimeoutException;
Madan Jampani307a21e2016-09-01 15:49:47 -070052import java.util.function.BinaryOperator;
Madan Jampania29c6772015-08-17 13:17:07 -070053import java.util.function.Function;
54import java.util.function.Supplier;
Sho SHIMIZU85803e22016-01-13 21:53:43 -080055import java.util.stream.Collectors;
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -070056import java.util.stream.Stream;
57import java.util.stream.StreamSupport;
tom5f38b3a2014-08-27 23:50:54 -070058
Jonathan Hartcc962d82016-08-09 16:52:22 -070059import static java.nio.file.Files.delete;
60import static java.nio.file.Files.walkFileTree;
61import static org.onlab.util.GroupedThreadFactory.groupedThreadFactory;
62import static org.slf4j.LoggerFactory.getLogger;
Ray Milkey705d9bc2014-11-18 08:19:00 -080063
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080064/**
65 * Miscellaneous utility methods.
66 */
tom5f38b3a2014-08-27 23:50:54 -070067public abstract class Tools {
68
69 private Tools() {
70 }
71
Thomas Vachuska02aeb032015-01-06 22:36:30 -080072 private static final Logger log = getLogger(Tools.class);
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080073
Thomas Vachuskaadba1522015-06-04 15:08:30 -070074 private static Random random = new Random();
75
Ray Milkeyb784adb2018-04-02 15:33:07 -070076 private static final String INPUT_JSON_CANNOT_BE_NULL = "Input JSON cannot be null";
77
tom5f38b3a2014-08-27 23:50:54 -070078 /**
79 * Returns a thread factory that produces threads named according to the
80 * supplied name pattern.
81 *
82 * @param pattern name pattern
83 * @return thread factory
84 */
85 public static ThreadFactory namedThreads(String pattern) {
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080086 return new ThreadFactoryBuilder()
87 .setNameFormat(pattern)
Thomas Vachuska480adad2015-03-06 10:27:09 -080088 .setUncaughtExceptionHandler((t, e) -> log.error("Uncaught exception on " + t.getName(), e))
89 .build();
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080090 }
Yuta HIGUCHI683e9782014-11-25 17:26:36 -080091
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080092 /**
93 * Returns a thread factory that produces threads named according to the
94 * supplied name pattern and from the specified thread-group. The thread
95 * group name is expected to be specified in slash-delimited format, e.g.
Thomas Vachuskac13b90a2015-02-18 18:19:55 -080096 * {@code onos/intent}. The thread names will be produced by converting
97 * the thread group name into dash-delimited format and pre-pended to the
98 * specified pattern.
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -080099 *
100 * @param groupName group name in slash-delimited format to indicate hierarchy
101 * @param pattern name pattern
102 * @return thread factory
103 */
104 public static ThreadFactory groupedThreads(String groupName, String pattern) {
Jian Li03e9fb02016-03-01 17:13:54 -0800105 return groupedThreads(groupName, pattern, log);
106 }
107
108 /**
109 * Returns a thread factory that produces threads named according to the
110 * supplied name pattern and from the specified thread-group. The thread
111 * group name is expected to be specified in slash-delimited format, e.g.
112 * {@code onos/intent}. The thread names will be produced by converting
113 * the thread group name into dash-delimited format and pre-pended to the
114 * specified pattern. If a logger is specified, it will use the logger to
115 * print out the exception if it has any.
116 *
117 * @param groupName group name in slash-delimited format to indicate hierarchy
118 * @param pattern name pattern
119 * @param logger logger
120 * @return thread factory
121 */
122 public static ThreadFactory groupedThreads(String groupName, String pattern, Logger logger) {
123 if (logger == null) {
124 return groupedThreads(groupName, pattern);
125 }
Thomas Vachuska9c17a6d2015-02-17 23:36:43 -0800126 return new ThreadFactoryBuilder()
127 .setThreadFactory(groupedThreadFactory(groupName))
Thomas Vachuskac13b90a2015-02-18 18:19:55 -0800128 .setNameFormat(groupName.replace(GroupedThreadFactory.DELIMITER, "-") + "-" + pattern)
Jian Li03e9fb02016-03-01 17:13:54 -0800129 .setUncaughtExceptionHandler((t, e) -> logger.error("Uncaught exception on " + t.getName(), e))
Thomas Vachuska480adad2015-03-06 10:27:09 -0800130 .build();
tom5f38b3a2014-08-27 23:50:54 -0700131 }
132
tom782a7cf2014-09-11 23:58:38 -0700133 /**
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800134 * Returns a thread factory that produces threads with MIN_PRIORITY.
135 *
136 * @param factory backing ThreadFactory
137 * @return thread factory
138 */
139 public static ThreadFactory minPriority(ThreadFactory factory) {
140 return new ThreadFactoryBuilder()
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800141 .setThreadFactory(factory)
142 .setPriority(Thread.MIN_PRIORITY)
143 .build();
Yuta HIGUCHI06586272014-11-25 14:27:03 -0800144 }
145
146 /**
Yuta HIGUCHIa2a11cd2016-12-19 14:19:11 -0800147 * Returns a thread factory that produces threads with MAX_PRIORITY.
148 *
149 * @param factory backing ThreadFactory
150 * @return thread factory
151 */
152 public static ThreadFactory maxPriority(ThreadFactory factory) {
153 return new ThreadFactoryBuilder()
154 .setThreadFactory(factory)
155 .setPriority(Thread.MAX_PRIORITY)
156 .build();
157 }
158
159 /**
Brian O'Connore2eac102015-02-12 18:30:22 -0800160 * Returns true if the collection is null or is empty.
161 *
162 * @param collection collection to test
163 * @return true if null or empty; false otherwise
164 */
165 public static boolean isNullOrEmpty(Collection collection) {
166 return collection == null || collection.isEmpty();
167 }
168
169 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700170 * Returns the specified item if that item is not null; otherwise throws
Thomas Vachuskaca88bb72015-04-08 19:38:02 -0700171 * not found exception.
172 *
173 * @param item item to check
174 * @param message not found message
175 * @param <T> item type
176 * @return item if not null
177 * @throws org.onlab.util.ItemNotFoundException if item is null
178 */
179 public static <T> T nullIsNotFound(T item, String message) {
180 if (item == null) {
181 throw new ItemNotFoundException(message);
182 }
183 return item;
184 }
185
186 /**
Ray Milkey36992c82015-11-17 13:31:15 -0800187 * Returns the specified set if the set is not null and not empty;
188 * otherwise throws a not found exception.
189 *
190 * @param item set to check
191 * @param message not found message
192 * @param <T> Set item type
193 * @return item if not null and not empty
194 * @throws org.onlab.util.ItemNotFoundException if set is null or empty
195 */
196 public static <T> Set<T> emptyIsNotFound(Set<T> item, String message) {
197 if (item == null || item.isEmpty()) {
198 throw new ItemNotFoundException(message);
199 }
200 return item;
201 }
202
203 /**
Ray Milkeyd43fe452015-05-29 09:35:12 -0700204 * Returns the specified item if that item is not null; otherwise throws
205 * bad argument exception.
206 *
207 * @param item item to check
208 * @param message not found message
209 * @param <T> item type
210 * @return item if not null
211 * @throws IllegalArgumentException if item is null
212 */
213 public static <T> T nullIsIllegal(T item, String message) {
214 if (item == null) {
215 throw new IllegalArgumentException(message);
216 }
217 return item;
218 }
219
220 /**
Ray Milkeyb784adb2018-04-02 15:33:07 -0700221 * Utility to convert a mapper and an input stream into a JSON tree,
222 * and be tolerant of a null tree being returned.
223 *
224 * @param mapper JSON object mapper
225 * @param stream IO stream containing the JSON
226 * @return object node for the given
227 * @throws IOException if JSON parsing fails
228 */
229 public static ObjectNode readTreeFromStream(ObjectMapper mapper, InputStream stream) throws IOException {
230 return nullIsIllegal((ObjectNode) mapper.readTree(stream), INPUT_JSON_CANNOT_BE_NULL);
231 }
232
233 /**
tom782a7cf2014-09-11 23:58:38 -0700234 * Converts a string from hex to long.
235 *
236 * @param string hex number in string form; sans 0x
237 * @return long value
238 */
239 public static long fromHex(String string) {
240 return UnsignedLongs.parseUnsignedLong(string, 16);
241 }
242
243 /**
244 * Converts a long value to hex string; 16 wide and sans 0x.
245 *
246 * @param value long value
247 * @return hex string
248 */
249 public static String toHex(long value) {
250 return Strings.padStart(UnsignedLongs.toString(value, 16), 16, '0');
251 }
252
253 /**
254 * Converts a long value to hex string; 16 wide and sans 0x.
255 *
256 * @param value long value
257 * @param width string width; zero padded
258 * @return hex string
259 */
260 public static String toHex(long value, int width) {
261 return Strings.padStart(UnsignedLongs.toString(value, 16), width, '0');
262 }
tomf110fff2014-09-26 00:38:18 -0700263
264 /**
Jonathan Hartcc962d82016-08-09 16:52:22 -0700265 * Returns a string encoding in hex of the given long value with prefix
266 * '0x'.
267 *
268 * @param value long value to encode as hex string
269 * @return hex string
270 */
271 public static String toHexWithPrefix(long value) {
272 return "0x" + Long.toHexString(value);
273 }
274
275 /**
Madan Jampanif2f086c2016-01-13 16:15:39 -0800276 * Returns the UTF-8 encoded byte[] representation of a String.
Jian Lidfba7392016-01-22 16:46:58 -0800277 * @param input input string
278 * @return UTF-8 encoded byte array
Madan Jampanif2f086c2016-01-13 16:15:39 -0800279 */
280 public static byte[] getBytesUtf8(String input) {
281 return input.getBytes(Charsets.UTF_8);
282 }
283
284 /**
285 * Returns the String representation of UTF-8 encoded byte[].
Jian Lidfba7392016-01-22 16:46:58 -0800286 * @param input input byte array
287 * @return UTF-8 encoded string
Madan Jampanif2f086c2016-01-13 16:15:39 -0800288 */
289 public static String toStringUtf8(byte[] input) {
290 return new String(input, Charsets.UTF_8);
291 }
292
293 /**
Madan Jampani9eb55d12015-08-14 07:47:56 -0700294 * Returns a copy of the input byte array.
295 *
296 * @param original input
297 * @return copy of original
298 */
299 public static byte[] copyOf(byte[] original) {
300 return Arrays.copyOf(original, original.length);
301 }
302
303 /**
Thomas Vachuska6519e6f2015-03-11 02:29:31 -0700304 * Get property as a string value.
305 *
306 * @param properties properties to be looked up
307 * @param propertyName the name of the property to look up
308 * @return value when the propertyName is defined or return null
309 */
310 public static String get(Dictionary<?, ?> properties, String propertyName) {
311 Object v = properties.get(propertyName);
312 String s = (v instanceof String) ? (String) v :
313 v != null ? v.toString() : null;
314 return Strings.isNullOrEmpty(s) ? null : s.trim();
315 }
316
317 /**
Jian Lid9b5f552016-03-11 18:15:31 -0800318 * Get Integer property from the propertyName
319 * Return null if propertyName is not found.
320 *
321 * @param properties properties to be looked up
322 * @param propertyName the name of the property to look up
323 * @return value when the propertyName is defined or return null
324 */
325 public static Integer getIntegerProperty(Dictionary<?, ?> properties,
326 String propertyName) {
327 Integer value;
328 try {
329 String s = get(properties, propertyName);
330 value = Strings.isNullOrEmpty(s) ? null : Integer.valueOf(s);
331 } catch (NumberFormatException | ClassCastException e) {
332 value = null;
333 }
334 return value;
335 }
336
337 /**
338 * Get Integer property from the propertyName
339 * Return default value if propertyName is not found.
340 *
341 * @param properties properties to be looked up
342 * @param propertyName the name of the property to look up
343 * @param defaultValue the default value that to be assigned
344 * @return value when the propertyName is defined or return default value
345 */
346 public static int getIntegerProperty(Dictionary<?, ?> properties,
347 String propertyName,
348 int defaultValue) {
349 try {
350 String s = get(properties, propertyName);
351 return Strings.isNullOrEmpty(s) ? defaultValue : Integer.valueOf(s);
352 } catch (NumberFormatException | ClassCastException e) {
353 return defaultValue;
354 }
355 }
356
357 /**
358 * Check property name is defined and set to true.
359 *
360 * @param properties properties to be looked up
361 * @param propertyName the name of the property to look up
362 * @return value when the propertyName is defined or return null
363 */
364 public static Boolean isPropertyEnabled(Dictionary<?, ?> properties,
365 String propertyName) {
366 Boolean value;
367 try {
368 String s = get(properties, propertyName);
369 value = Strings.isNullOrEmpty(s) ? null : Boolean.valueOf(s);
370 } catch (ClassCastException e) {
371 value = null;
372 }
373 return value;
374 }
375
376 /**
377 * Check property name is defined as set to true.
378 *
379 * @param properties properties to be looked up
380 * @param propertyName the name of the property to look up
381 * @param defaultValue the default value that to be assigned
382 * @return value when the propertyName is defined or return the default value
383 */
384 public static boolean isPropertyEnabled(Dictionary<?, ?> properties,
385 String propertyName,
386 boolean defaultValue) {
387 try {
388 String s = get(properties, propertyName);
389 return Strings.isNullOrEmpty(s) ? defaultValue : Boolean.valueOf(s);
390 } catch (ClassCastException e) {
391 return defaultValue;
392 }
393 }
394
395 /**
tomf110fff2014-09-26 00:38:18 -0700396 * Suspends the current thread for a specified number of millis.
397 *
398 * @param ms number of millis
399 */
400 public static void delay(int ms) {
401 try {
402 Thread.sleep(ms);
403 } catch (InterruptedException e) {
404 throw new RuntimeException("Interrupted", e);
405 }
406 }
407
tom53efab52014-10-07 17:43:48 -0700408 /**
sdn94b00152016-08-30 02:12:32 -0700409 * Get Long property from the propertyName
410 * Return null if propertyName is not found.
411 *
412 * @param properties properties to be looked up
413 * @param propertyName the name of the property to look up
414 * @return value when the propertyName is defined or return null
415 */
416 public static Long getLongProperty(Dictionary<?, ?> properties,
417 String propertyName) {
418 Long value;
419 try {
420 String s = get(properties, propertyName);
421 value = Strings.isNullOrEmpty(s) ? null : Long.valueOf(s);
422 } catch (NumberFormatException | ClassCastException e) {
423 value = null;
424 }
425 return value;
426 }
427
428 /**
Madan Jampania29c6772015-08-17 13:17:07 -0700429 * Returns a function that retries execution on failure.
430 * @param base base function
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 function
Thomas Vachuska87ae1d92015-08-19 17:39:11 -0700436 * @param <U> type of function input
437 * @param <V> type of function output
Madan Jampania29c6772015-08-17 13:17:07 -0700438 */
439 public static <U, V> Function<U, V> retryable(Function<U, V> base,
440 Class<? extends Throwable> exceptionClass,
441 int maxRetries,
442 int maxDelayBetweenRetries) {
443 return new RetryingFunction<>(base, exceptionClass, maxRetries, maxDelayBetweenRetries);
444 }
445
446 /**
447 * Returns a Supplier that retries execution on failure.
448 * @param base base supplier
449 * @param exceptionClass type of exception for which to retry
450 * @param maxRetries max number of retries before giving up
451 * @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from
452 * the interval (0, maxDelayBetweenRetries]
453 * @return supplier
Thomas Vachuska87ae1d92015-08-19 17:39:11 -0700454 * @param <V> type of supplied result
Madan Jampania29c6772015-08-17 13:17:07 -0700455 */
456 public static <V> Supplier<V> retryable(Supplier<V> base,
457 Class<? extends Throwable> exceptionClass,
458 int maxRetries,
459 int maxDelayBetweenRetries) {
460 return () -> new RetryingFunction<>(v -> base.get(),
461 exceptionClass,
462 maxRetries,
463 maxDelayBetweenRetries).apply(null);
464 }
465
466 /**
Thomas Vachuskaadba1522015-06-04 15:08:30 -0700467 * Suspends the current thread for a random number of millis between 0 and
468 * the indicated limit.
469 *
470 * @param ms max number of millis
471 */
472 public static void randomDelay(int ms) {
473 try {
474 Thread.sleep(random.nextInt(ms));
475 } catch (InterruptedException e) {
476 throw new RuntimeException("Interrupted", e);
477 }
478 }
479
480 /**
Thomas Vachuskac40d4632015-04-09 16:55:03 -0700481 * Suspends the current thread for a specified number of millis and nanos.
482 *
483 * @param ms number of millis
484 * @param nanos number of nanos
485 */
486 public static void delay(int ms, int nanos) {
487 try {
488 Thread.sleep(ms, nanos);
489 } catch (InterruptedException e) {
490 throw new RuntimeException("Interrupted", e);
491 }
492 }
493
494 /**
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800495 * Purges the specified directory path.&nbsp;Use with great caution since
496 * no attempt is made to check for symbolic links, which could result in
497 * deletion of unintended files.
498 *
499 * @param path directory to be removed
500 * @throws java.io.IOException if unable to remove contents
501 */
502 public static void removeDirectory(String path) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800503 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700504 File dir = new File(path);
505 if (dir.exists() && dir.isDirectory()) {
506 walkFileTree(Paths.get(path), 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
513 /**
514 * Purges the specified directory path.&nbsp;Use with great caution since
515 * no attempt is made to check for symbolic links, which could result in
516 * deletion of unintended files.
517 *
518 * @param dir directory to be removed
519 * @throws java.io.IOException if unable to remove contents
520 */
521 public static void removeDirectory(File dir) throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800522 DirectoryDeleter visitor = new DirectoryDeleter();
Thomas Vachuskaf9c84362015-04-15 11:20:45 -0700523 if (dir.exists() && dir.isDirectory()) {
524 walkFileTree(Paths.get(dir.getAbsolutePath()), visitor);
525 if (visitor.exception != null) {
526 throw visitor.exception;
527 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800528 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800529 }
530
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800531 // Auxiliary path visitor for recursive directory structure removal.
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800532 private static class DirectoryDeleter extends SimpleFileVisitor<Path> {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800533
534 private IOException exception;
535
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800536 @Override
537 public FileVisitResult visitFile(Path file, BasicFileAttributes attributes)
538 throws IOException {
539 if (attributes.isRegularFile()) {
540 delete(file);
541 }
542 return FileVisitResult.CONTINUE;
543 }
544
545 @Override
546 public FileVisitResult postVisitDirectory(Path directory, IOException ioe)
547 throws IOException {
548 delete(directory);
549 return FileVisitResult.CONTINUE;
550 }
551
552 @Override
553 public FileVisitResult visitFileFailed(Path file, IOException ioe)
554 throws IOException {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800555 this.exception = ioe;
556 return FileVisitResult.TERMINATE;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800557 }
558 }
559
Madan Jampani30a57f82015-03-02 12:19:41 -0800560 /**
561 * Returns a human friendly time ago string for a specified system time.
Thomas Vachuska480adad2015-03-06 10:27:09 -0800562 *
Madan Jampani30a57f82015-03-02 12:19:41 -0800563 * @param unixTime system time in millis
564 * @return human friendly time ago
565 */
566 public static String timeAgo(long unixTime) {
567 long deltaMillis = System.currentTimeMillis() - unixTime;
568 long secondsSince = (long) (deltaMillis / 1000.0);
569 long minsSince = (long) (deltaMillis / (1000.0 * 60));
570 long hoursSince = (long) (deltaMillis / (1000.0 * 60 * 60));
571 long daysSince = (long) (deltaMillis / (1000.0 * 60 * 60 * 24));
572 if (daysSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800573 return String.format("%dd%dh ago", daysSince, hoursSince - daysSince * 24);
Madan Jampani30a57f82015-03-02 12:19:41 -0800574 } else if (hoursSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800575 return String.format("%dh%dm ago", hoursSince, minsSince - hoursSince * 60);
Madan Jampani30a57f82015-03-02 12:19:41 -0800576 } else if (minsSince > 0) {
Saurav Dasd5ec9e92017-01-17 10:40:18 -0800577 return String.format("%dm%ds ago", minsSince, secondsSince - minsSince * 60);
Madan Jampani30a57f82015-03-02 12:19:41 -0800578 } else if (secondsSince > 0) {
579 return String.format("%ds ago", secondsSince);
580 } else {
581 return "just now";
582 }
583 }
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800584
585 /**
586 * Copies the specified directory path.&nbsp;Use with great caution since
587 * no attempt is made to check for symbolic links, which could result in
588 * copy of unintended files.
589 *
590 * @param src directory to be copied
591 * @param dst destination directory to be removed
592 * @throws java.io.IOException if unable to remove contents
593 */
594 public static void copyDirectory(String src, String dst) throws IOException {
595 walkFileTree(Paths.get(src), new DirectoryCopier(src, dst));
596 }
597
598 /**
599 * Copies the specified directory path.&nbsp;Use with great caution since
600 * no attempt is made to check for symbolic links, which could result in
601 * copy of unintended files.
602 *
603 * @param src directory to be copied
604 * @param dst destination directory to be removed
605 * @throws java.io.IOException if unable to remove contents
606 */
607 public static void copyDirectory(File src, File dst) throws IOException {
608 walkFileTree(Paths.get(src.getAbsolutePath()),
609 new DirectoryCopier(src.getAbsolutePath(),
610 dst.getAbsolutePath()));
611 }
612
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700613 /**
614 * Returns the future value when complete or if future
615 * completes exceptionally returns the defaultValue.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700616 *
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700617 * @param future future
618 * @param defaultValue default value
619 * @param <T> future value type
620 * @return future value when complete or if future
621 * completes exceptionally returns the defaultValue.
622 */
623 public static <T> T futureGetOrElse(Future<T> future, T defaultValue) {
624 try {
625 return future.get();
626 } catch (InterruptedException e) {
627 Thread.currentThread().interrupt();
628 return defaultValue;
629 } catch (ExecutionException e) {
630 return defaultValue;
631 }
632 }
633
634 /**
635 * Returns the future value when complete or if future
636 * completes exceptionally returns the defaultValue.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700637 *
Madan Jampani2bfa94c2015-04-11 05:03:49 -0700638 * @param future future
639 * @param timeout time to wait for successful completion
640 * @param timeUnit time unit
641 * @param defaultValue default value
642 * @param <T> future value type
643 * @return future value when complete or if future
644 * completes exceptionally returns the defaultValue.
645 */
646 public static <T> T futureGetOrElse(Future<T> future,
647 long timeout,
648 TimeUnit timeUnit,
649 T defaultValue) {
650 try {
651 return future.get(timeout, timeUnit);
652 } catch (InterruptedException e) {
653 Thread.currentThread().interrupt();
654 return defaultValue;
655 } catch (ExecutionException | TimeoutException e) {
656 return defaultValue;
657 }
658 }
659
Madan Jampani27b69c62015-05-15 15:49:02 -0700660 /**
661 * Returns a future that is completed exceptionally.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700662 *
Madan Jampani27b69c62015-05-15 15:49:02 -0700663 * @param t exception
664 * @param <T> future value type
665 * @return future
666 */
667 public static <T> CompletableFuture<T> exceptionalFuture(Throwable t) {
668 CompletableFuture<T> future = new CompletableFuture<>();
669 future.completeExceptionally(t);
670 return future;
671 }
672
673 /**
Jordan Halterman046faeb2017-05-01 15:10:13 -0700674 * Returns a future that's completed using the given {@code orderedExecutor} if the future is not blocked or the
675 * given {@code threadPoolExecutor} if the future is blocked.
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700676 * <p>
Jordan Halterman046faeb2017-05-01 15:10:13 -0700677 * This method allows futures to maintain single-thread semantics via the provided {@code orderedExecutor} while
678 * ensuring user code can block without blocking completion of futures. When the returned future or any of its
679 * descendants is blocked on a {@link CompletableFuture#get()} or {@link CompletableFuture#join()} call, completion
680 * of the returned future will be done using the provided {@code threadPoolExecutor}.
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700681 *
682 * @param future the future to convert into an asynchronous future
Jordan Halterman046faeb2017-05-01 15:10:13 -0700683 * @param orderedExecutor the ordered executor with which to attempt to complete the future
684 * @param threadPoolExecutor the backup executor with which to complete blocked futures
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700685 * @param <T> future value type
686 * @return a new completable future to be completed using the provided {@code executor} once the provided
687 * {@code future} is complete
688 */
Jordan Halterman046faeb2017-05-01 15:10:13 -0700689 public static <T> CompletableFuture<T> orderedFuture(
690 CompletableFuture<T> future,
691 Executor orderedExecutor,
692 Executor threadPoolExecutor) {
Jordan Haltermane265d372017-05-17 22:40:47 -0700693 if (future.isDone()) {
694 return future;
695 }
696
Jordan Halterman046faeb2017-05-01 15:10:13 -0700697 BlockingAwareFuture<T> newFuture = new BlockingAwareFuture<T>();
698 future.whenComplete((result, error) -> {
699 Runnable completer = () -> {
700 if (future.isCompletedExceptionally()) {
701 newFuture.completeExceptionally(error);
702 } else {
703 newFuture.complete(result);
704 }
705 };
706
707 if (newFuture.isBlocked()) {
708 threadPoolExecutor.execute(completer);
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700709 } else {
Jordan Halterman046faeb2017-05-01 15:10:13 -0700710 orderedExecutor.execute(completer);
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700711 }
Jordan Halterman046faeb2017-05-01 15:10:13 -0700712 });
Jordan Halterman9bdc24f2017-04-19 23:45:12 -0700713 return newFuture;
714 }
715
716 /**
Sho SHIMIZU85803e22016-01-13 21:53:43 -0800717 * Returns a new CompletableFuture completed with a list of computed values
718 * when all of the given CompletableFuture complete.
719 *
720 * @param futures the CompletableFutures
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<List<T>> allOf(List<CompletableFuture<T>> futures) {
725 return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]))
726 .thenApply(v -> futures.stream()
727 .map(CompletableFuture::join)
728 .collect(Collectors.toList())
729 );
730 }
731
732 /**
Madan Jampani307a21e2016-09-01 15:49:47 -0700733 * Returns a new CompletableFuture completed by reducing a list of computed values
734 * when all of the given CompletableFuture complete.
735 *
736 * @param futures the CompletableFutures
737 * @param reducer reducer for computing the result
738 * @param emptyValue zero value to be returned if the input future list is empty
739 * @param <T> value type of CompletableFuture
740 * @return a new CompletableFuture that is completed when all of the given CompletableFutures complete
741 */
742 public static <T> CompletableFuture<T> allOf(List<CompletableFuture<T>> futures,
743 BinaryOperator<T> reducer,
744 T emptyValue) {
745 return Tools.allOf(futures)
746 .thenApply(resultList -> resultList.stream().reduce(reducer).orElse(emptyValue));
747 }
748
749 /**
750 * Returns a new CompletableFuture completed by with the first positive result from a list of
751 * input CompletableFutures.
752 *
753 * @param futures the input list of CompletableFutures
754 * @param positiveResultMatcher matcher to identify a positive result
755 * @param negativeResult value to complete with if none of the futures complete with a positive result
756 * @param <T> value type of CompletableFuture
757 * @return a new CompletableFuture
758 */
759 public static <T> CompletableFuture<T> firstOf(List<CompletableFuture<T>> futures,
760 Match<T> positiveResultMatcher,
761 T negativeResult) {
762 CompletableFuture<T> responseFuture = new CompletableFuture<>();
763 Tools.allOf(Lists.transform(futures, future -> future.thenAccept(r -> {
764 if (positiveResultMatcher.matches(r)) {
765 responseFuture.complete(r);
766 }
767 }))).whenComplete((r, e) -> {
768 if (!responseFuture.isDone()) {
769 if (e != null) {
770 responseFuture.completeExceptionally(e);
771 } else {
772 responseFuture.complete(negativeResult);
773 }
774 }
775 });
776 return responseFuture;
777 }
778
779 /**
Madan Jampani27b69c62015-05-15 15:49:02 -0700780 * Returns the contents of {@code ByteBuffer} as byte array.
781 * <p>
782 * WARNING: There is a performance cost due to array copy
783 * when using this method.
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700784 *
Madan Jampani27b69c62015-05-15 15:49:02 -0700785 * @param buffer byte buffer
786 * @return byte array containing the byte buffer contents
787 */
788 public static byte[] byteBuffertoArray(ByteBuffer buffer) {
789 int length = buffer.remaining();
790 if (buffer.hasArray()) {
791 int offset = buffer.arrayOffset() + buffer.position();
792 return Arrays.copyOfRange(buffer.array(), offset, offset + length);
793 }
794 byte[] bytes = new byte[length];
795 buffer.duplicate().get(bytes);
796 return bytes;
797 }
798
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700799 /**
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700800 * Converts an iterable to a stream.
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700801 *
Thomas Vachuskad894b5d2015-07-30 11:59:07 -0700802 * @param it iterable to convert
803 * @param <T> type if item
804 * @return iterable as a stream
HIGUCHI Yutabfc8b7a2015-07-01 23:47:43 -0700805 */
806 public static <T> Stream<T> stream(Iterable<T> it) {
807 return StreamSupport.stream(it.spliterator(), false);
808 }
809
Sho SHIMIZUb5638b82016-02-11 14:55:05 -0800810 /**
811 * Converts an optional to a stream.
812 *
813 * @param optional optional to convert
814 * @param <T> type of enclosed value
815 * @return optional as a stream
816 */
Sho SHIMIZU6ac20982016-05-04 09:50:54 -0700817 public static <T> Stream<T> stream(Optional<? extends T> optional) {
HIGUCHI Yuta0bc256f2016-05-06 15:28:26 -0700818 return optional.map(x -> Stream.<T>of(x)).orElse(Stream.empty());
Sho SHIMIZUb5638b82016-02-11 14:55:05 -0800819 }
820
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800821 // Auxiliary path visitor for recursive directory structure copying.
822 private static class DirectoryCopier extends SimpleFileVisitor<Path> {
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800823 private Path src;
824 private Path dst;
825 private StandardCopyOption copyOption = StandardCopyOption.REPLACE_EXISTING;
826
827 DirectoryCopier(String src, String dst) {
828 this.src = Paths.get(src);
829 this.dst = Paths.get(dst);
830 }
831
832 @Override
833 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
834 Path targetPath = dst.resolve(src.relativize(dir));
835 if (!Files.exists(targetPath)) {
836 Files.createDirectory(targetPath);
837 }
838 return FileVisitResult.CONTINUE;
839 }
840
841 @Override
842 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
843 Files.copy(file, dst.resolve(src.relativize(file)), copyOption);
844 return FileVisitResult.CONTINUE;
845 }
846 }
847
tom5f38b3a2014-08-27 23:50:54 -0700848}