org.onlab.util.Tools Maven / Gradle / Ivy
/*
* Copyright 2014-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.util;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.primitives.UnsignedLongs;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.slf4j.Logger;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.SecureRandom;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Collection;
import java.util.Dictionary;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.file.Files.delete;
import static java.nio.file.Files.walkFileTree;
import static org.onlab.util.GroupedThreadFactory.groupedThreadFactory;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Miscellaneous utility methods.
*/
public abstract class Tools {
private Tools() {
}
private static final Logger log = getLogger(Tools.class);
private static Random random = new SecureRandom();
private static final String INPUT_JSON_CANNOT_BE_NULL = "Input JSON cannot be null";
/**
* Returns a thread factory that produces threads named according to the
* supplied name pattern.
*
* @param pattern name pattern
* @return thread factory
*/
public static ThreadFactory namedThreads(String pattern) {
return new ThreadFactoryBuilder()
.setNameFormat(pattern)
.setUncaughtExceptionHandler((t, e) -> log.error("Uncaught exception on " + t.getName(), e))
.build();
}
/**
* Returns a thread factory that produces threads named according to the
* supplied name pattern and from the specified thread-group. The thread
* group name is expected to be specified in slash-delimited format, e.g.
* {@code onos/intent}. The thread names will be produced by converting
* the thread group name into dash-delimited format and pre-pended to the
* specified pattern.
*
* @param groupName group name in slash-delimited format to indicate hierarchy
* @param pattern name pattern
* @return thread factory
*/
public static ThreadFactory groupedThreads(String groupName, String pattern) {
return groupedThreads(groupName, pattern, log);
}
/**
* Returns a thread factory that produces threads named according to the
* supplied name pattern and from the specified thread-group. The thread
* group name is expected to be specified in slash-delimited format, e.g.
* {@code onos/intent}. The thread names will be produced by converting
* the thread group name into dash-delimited format and pre-pended to the
* specified pattern. If a logger is specified, it will use the logger to
* print out the exception if it has any.
*
* @param groupName group name in slash-delimited format to indicate hierarchy
* @param pattern name pattern
* @param logger logger
* @return thread factory
*/
public static ThreadFactory groupedThreads(String groupName, String pattern, Logger logger) {
if (logger == null) {
return groupedThreads(groupName, pattern);
}
return new ThreadFactoryBuilder()
.setThreadFactory(groupedThreadFactory(groupName))
.setNameFormat(groupName.replace(GroupedThreadFactory.DELIMITER, "-") + "-" + pattern)
.setUncaughtExceptionHandler((t, e) -> logger.error("Uncaught exception on " + t.getName(), e))
.build();
}
/**
* Returns a thread factory that produces threads with MIN_PRIORITY.
*
* @param factory backing ThreadFactory
* @return thread factory
*/
public static ThreadFactory minPriority(ThreadFactory factory) {
return new ThreadFactoryBuilder()
.setThreadFactory(factory)
.setPriority(Thread.MIN_PRIORITY)
.build();
}
/**
* Returns a thread factory that produces threads with MAX_PRIORITY.
*
* @param factory backing ThreadFactory
* @return thread factory
*/
public static ThreadFactory maxPriority(ThreadFactory factory) {
return new ThreadFactoryBuilder()
.setThreadFactory(factory)
.setPriority(Thread.MAX_PRIORITY)
.build();
}
/**
* Returns true if the collection is null or is empty.
*
* @param collection collection to test
* @return true if null or empty; false otherwise
*/
public static boolean isNullOrEmpty(Collection> collection) {
return collection == null || collection.isEmpty();
}
/**
* Returns the specified item if that item is not null; otherwise throws
* not found exception.
*
* @param item item to check
* @param message not found message
* @param item type
* @return item if not null
* @throws org.onlab.util.ItemNotFoundException if item is null
*/
public static T nullIsNotFound(T item, String message) {
if (item == null) {
throw new ItemNotFoundException(message);
}
return item;
}
/**
* Returns the specified set if the set is not null and not empty;
* otherwise throws a not found exception.
*
* @param item set to check
* @param message not found message
* @param Set item type
* @return item if not null and not empty
* @throws org.onlab.util.ItemNotFoundException if set is null or empty
*/
public static Set emptyIsNotFound(Set item, String message) {
if (item == null || item.isEmpty()) {
throw new ItemNotFoundException(message);
}
return item;
}
/**
* Returns the specified item if that item is not null; otherwise throws
* bad argument exception.
*
* @param item item to check
* @param message not found message
* @param item type
* @return item if not null
* @throws IllegalArgumentException if item is null
*/
public static T nullIsIllegal(T item, String message) {
if (item == null) {
throw new IllegalArgumentException(message);
}
return item;
}
/**
* Utility to convert a mapper and an input stream into a JSON tree,
* and be tolerant of a null tree being returned.
*
* @param mapper JSON object mapper
* @param stream IO stream containing the JSON
* @return object node for the given
* @throws IOException if JSON parsing fails
*/
public static ObjectNode readTreeFromStream(ObjectMapper mapper, InputStream stream) throws IOException {
return nullIsIllegal((ObjectNode) mapper.readTree(stream), INPUT_JSON_CANNOT_BE_NULL);
}
/**
* Converts a string from hex to long.
*
* @param string hex number in string form; sans 0x
* @return long value
*/
public static long fromHex(String string) {
return UnsignedLongs.parseUnsignedLong(string, 16);
}
/**
* Converts a long value to hex string; 16 wide and sans 0x.
*
* @param value long value
* @return hex string
*/
public static String toHex(long value) {
return Strings.padStart(UnsignedLongs.toString(value, 16), 16, '0');
}
/**
* Converts a long value to hex string; 16 wide and sans 0x.
*
* @param value long value
* @param width string width; zero padded
* @return hex string
*/
public static String toHex(long value, int width) {
return Strings.padStart(UnsignedLongs.toString(value, 16), width, '0');
}
/**
* Returns a string encoding in hex of the given long value with prefix
* '0x'.
*
* @param value long value to encode as hex string
* @return hex string
*/
public static String toHexWithPrefix(long value) {
return "0x" + Long.toHexString(value);
}
/**
* Returns the UTF-8 encoded byte[] representation of a String.
* @param input input string
* @return UTF-8 encoded byte array
*/
public static byte[] getBytesUtf8(String input) {
return input.getBytes(Charsets.UTF_8);
}
/**
* Returns the String representation of UTF-8 encoded byte[].
* @param input input byte array
* @return UTF-8 encoded string
*/
public static String toStringUtf8(byte[] input) {
return new String(input, Charsets.UTF_8);
}
/**
* Returns a copy of the input byte array.
*
* @param original input
* @return copy of original
*/
public static byte[] copyOf(byte[] original) {
return Arrays.copyOf(original, original.length);
}
/**
* Get property as a string value.
*
* @param properties properties to be looked up
* @param propertyName the name of the property to look up
* @return value when the propertyName is defined or return null
*/
public static String get(Dictionary, ?> properties, String propertyName) {
Object v = properties.get(propertyName);
String s = (v instanceof String) ? (String) v :
v != null ? v.toString() : null;
return Strings.isNullOrEmpty(s) ? null : s.trim();
}
/**
* Get Integer property from the propertyName
* Return null if propertyName is not found.
*
* @param properties properties to be looked up
* @param propertyName the name of the property to look up
* @return value when the propertyName is defined or return null
*/
public static Integer getIntegerProperty(Dictionary, ?> properties,
String propertyName) {
Integer value;
try {
String s = get(properties, propertyName);
value = Strings.isNullOrEmpty(s) ? null : Integer.valueOf(s);
} catch (NumberFormatException | ClassCastException e) {
value = null;
}
return value;
}
/**
* Get Integer property from the propertyName
* Return default value if propertyName is not found.
*
* @param properties properties to be looked up
* @param propertyName the name of the property to look up
* @param defaultValue the default value that to be assigned
* @return value when the propertyName is defined or return default value
*/
public static int getIntegerProperty(Dictionary, ?> properties,
String propertyName,
int defaultValue) {
try {
String s = get(properties, propertyName);
return Strings.isNullOrEmpty(s) ? defaultValue : Integer.valueOf(s);
} catch (NumberFormatException | ClassCastException e) {
return defaultValue;
}
}
/**
* Check property name is defined and set to true.
*
* @param properties properties to be looked up
* @param propertyName the name of the property to look up
* @return value when the propertyName is defined or return null
*/
public static Boolean isPropertyEnabled(Dictionary, ?> properties,
String propertyName) {
Boolean value;
try {
String s = get(properties, propertyName);
value = Strings.isNullOrEmpty(s) ? null : Boolean.valueOf(s);
} catch (ClassCastException e) {
value = null;
}
return value;
}
/**
* Check property name is defined as set to true.
*
* @param properties properties to be looked up
* @param propertyName the name of the property to look up
* @param defaultValue the default value that to be assigned
* @return value when the propertyName is defined or return the default value
*/
public static boolean isPropertyEnabled(Dictionary, ?> properties,
String propertyName,
boolean defaultValue) {
try {
String s = get(properties, propertyName);
return Strings.isNullOrEmpty(s) ? defaultValue : Boolean.valueOf(s);
} catch (ClassCastException e) {
return defaultValue;
}
}
/**
* Suspends the current thread for a specified number of millis.
*
* @param ms number of millis
*/
public static void delay(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Interrupted", e);
}
}
/**
* Get Long property from the propertyName
* Return null if propertyName is not found.
*
* @param properties properties to be looked up
* @param propertyName the name of the property to look up
* @return value when the propertyName is defined or return null
*/
public static Long getLongProperty(Dictionary, ?> properties,
String propertyName) {
Long value;
try {
String s = get(properties, propertyName);
value = Strings.isNullOrEmpty(s) ? null : Long.valueOf(s);
} catch (NumberFormatException | ClassCastException e) {
value = null;
}
return value;
}
/**
* Get Float property from the propertyName
* Return null if propertyName is not found.
*
* @param properties properties to be looked up
* @param propertyName the name of the property to look up
* @return value when the propertyName is defined or return null
*/
public static Float getFloatProperty(Dictionary, ?> properties,
String propertyName) {
Float value;
try {
String s = get(properties, propertyName);
value = Strings.isNullOrEmpty(s) ? null : Float.valueOf(s);
} catch (NumberFormatException | ClassCastException e) {
value = null;
}
return value;
}
/**
* Returns a function that retries execution on failure.
* @param base base function
* @param exceptionClass type of exception for which to retry
* @param maxRetries max number of retries before giving up
* @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from
* the interval (0, maxDelayBetweenRetries]
* @return function
* @param type of function input
* @param type of function output
*/
public static Function retryable(Function base,
Class extends Throwable> exceptionClass,
int maxRetries,
int maxDelayBetweenRetries) {
return new RetryingFunction<>(base, exceptionClass, maxRetries, maxDelayBetweenRetries);
}
/**
* Returns a Supplier that retries execution on failure.
* @param base base supplier
* @param exceptionClass type of exception for which to retry
* @param maxRetries max number of retries before giving up
* @param maxDelayBetweenRetries max delay between successive retries. The actual delay is randomly picked from
* the interval (0, maxDelayBetweenRetries]
* @return supplier
* @param type of supplied result
*/
public static Supplier retryable(Supplier base,
Class extends Throwable> exceptionClass,
int maxRetries,
int maxDelayBetweenRetries) {
return () -> new RetryingFunction<>(v -> base.get(),
exceptionClass,
maxRetries,
maxDelayBetweenRetries).apply(null);
}
/**
* Suspends the current thread for a random number of millis between 0 and
* the indicated limit.
*
* @param ms max number of millis
*/
public static void randomDelay(int ms) {
try {
Thread.sleep(random.nextInt(ms));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Interrupted", e);
}
}
/**
* Suspends the current thread for a specified number of millis and nanos.
*
* @param ms number of millis
* @param nanos number of nanos
*/
public static void delay(int ms, int nanos) {
try {
Thread.sleep(ms, nanos);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Interrupted", e);
}
}
/**
* Purges the specified directory path. Use with great caution since
* no attempt is made to check for symbolic links, which could result in
* deletion of unintended files.
*
* @param path directory to be removed
* @throws java.io.IOException if unable to remove contents
*/
public static void removeDirectory(String path) throws IOException {
DirectoryDeleter visitor = new DirectoryDeleter();
File dir = new File(path);
if (dir.exists() && dir.isDirectory()) {
walkFileTree(Paths.get(path), visitor);
if (visitor.exception != null) {
throw visitor.exception;
}
}
}
/**
* Purges the specified directory path. Use with great caution since
* no attempt is made to check for symbolic links, which could result in
* deletion of unintended files.
*
* @param dir directory to be removed
* @throws java.io.IOException if unable to remove contents
*/
public static void removeDirectory(File dir) throws IOException {
DirectoryDeleter visitor = new DirectoryDeleter();
if (dir.exists() && dir.isDirectory()) {
walkFileTree(Paths.get(dir.getAbsolutePath()), visitor);
if (visitor.exception != null) {
throw visitor.exception;
}
}
}
// Auxiliary path visitor for recursive directory structure removal.
private static class DirectoryDeleter extends SimpleFileVisitor {
private IOException exception;
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attributes)
throws IOException {
if (attributes.isRegularFile()) {
delete(file);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path directory, IOException ioe)
throws IOException {
delete(directory);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException ioe)
throws IOException {
this.exception = ioe;
return FileVisitResult.TERMINATE;
}
}
/**
* Returns a human friendly time ago string for a specified system time.
*
* @param unixTime system time in millis
* @return human friendly time ago
*/
public static String timeAgo(long unixTime) {
long deltaMillis = System.currentTimeMillis() - unixTime;
long secondsSince = (long) (deltaMillis / 1000.0);
long minsSince = (long) (deltaMillis / (1000.0 * 60));
long hoursSince = (long) (deltaMillis / (1000.0 * 60 * 60));
long daysSince = (long) (deltaMillis / (1000.0 * 60 * 60 * 24));
if (daysSince > 0) {
return String.format("%dd%dh ago", daysSince, hoursSince - daysSince * 24);
} else if (hoursSince > 0) {
return String.format("%dh%dm ago", hoursSince, minsSince - hoursSince * 60);
} else if (minsSince > 0) {
return String.format("%dm%ds ago", minsSince, secondsSince - minsSince * 60);
} else if (secondsSince > 0) {
return String.format("%ds ago", secondsSince);
} else {
return "just now";
}
}
/**
* Copies the specified directory path. Use with great caution since
* no attempt is made to check for symbolic links, which could result in
* copy of unintended files.
*
* @param src directory to be copied
* @param dst destination directory to be removed
* @throws java.io.IOException if unable to remove contents
*/
public static void copyDirectory(String src, String dst) throws IOException {
walkFileTree(Paths.get(src), new DirectoryCopier(src, dst));
}
/**
* Copies the specified directory path. Use with great caution since
* no attempt is made to check for symbolic links, which could result in
* copy of unintended files.
*
* @param src directory to be copied
* @param dst destination directory to be removed
* @throws java.io.IOException if unable to remove contents
*/
public static void copyDirectory(File src, File dst) throws IOException {
walkFileTree(Paths.get(src.getAbsolutePath()),
new DirectoryCopier(src.getAbsolutePath(),
dst.getAbsolutePath()));
}
/**
* Returns the future value when complete or if future
* completes exceptionally returns the defaultValue.
*
* @param future future
* @param defaultValue default value
* @param future value type
* @return future value when complete or if future
* completes exceptionally returns the defaultValue.
*/
public static T futureGetOrElse(Future future, T defaultValue) {
try {
return future.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return defaultValue;
} catch (ExecutionException e) {
return defaultValue;
}
}
/**
* Returns the future value when complete or if future
* completes exceptionally returns the defaultValue.
*
* @param future future
* @param timeout time to wait for successful completion
* @param timeUnit time unit
* @param defaultValue default value
* @param future value type
* @return future value when complete or if future
* completes exceptionally returns the defaultValue.
*/
public static T futureGetOrElse(Future future,
long timeout,
TimeUnit timeUnit,
T defaultValue) {
try {
return future.get(timeout, timeUnit);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return defaultValue;
} catch (ExecutionException | TimeoutException e) {
return defaultValue;
}
}
/**
* Returns a future that is completed exceptionally.
*
* @param t exception
* @param future value type
* @return future
*/
public static CompletableFuture exceptionalFuture(Throwable t) {
CompletableFuture future = new CompletableFuture<>();
future.completeExceptionally(t);
return future;
}
/**
* Returns a future that's completed using the given {@code orderedExecutor} if the future is not blocked or the
* given {@code threadPoolExecutor} if the future is blocked.
*
* This method allows futures to maintain single-thread semantics via the provided {@code orderedExecutor} while
* ensuring user code can block without blocking completion of futures. When the returned future or any of its
* descendants is blocked on a {@link CompletableFuture#get()} or {@link CompletableFuture#join()} call, completion
* of the returned future will be done using the provided {@code threadPoolExecutor}.
*
* @param future the future to convert into an asynchronous future
* @param orderedExecutor the ordered executor with which to attempt to complete the future
* @param threadPoolExecutor the backup executor with which to complete blocked futures
* @param future value type
* @return a new completable future to be completed using the provided {@code executor} once the provided
* {@code future} is complete
*/
public static CompletableFuture orderedFuture(
CompletableFuture future,
Executor orderedExecutor,
Executor threadPoolExecutor) {
if (future.isDone()) {
return future;
}
BlockingAwareFuture newFuture = new BlockingAwareFuture<>();
future.whenComplete((result, error) -> {
Runnable completer = () -> {
if (future.isCompletedExceptionally()) {
newFuture.completeExceptionally(error);
} else {
newFuture.complete(result);
}
};
if (newFuture.isBlocked()) {
threadPoolExecutor.execute(completer);
} else {
orderedExecutor.execute(completer);
}
});
return newFuture;
}
/**
* Returns a new CompletableFuture completed with a list of computed values
* when all of the given CompletableFuture complete.
*
* @param futures the CompletableFutures
* @param value type of CompletableFuture
* @return a new CompletableFuture that is completed when all of the given CompletableFutures complete
*/
public static CompletableFuture> allOf(List> futures) {
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList())
);
}
/**
* Returns a new CompletableFuture completed by reducing a list of computed values
* when all of the given CompletableFuture complete.
*
* @param futures the CompletableFutures
* @param reducer reducer for computing the result
* @param emptyValue zero value to be returned if the input future list is empty
* @param value type of CompletableFuture
* @return a new CompletableFuture that is completed when all of the given CompletableFutures complete
*/
public static CompletableFuture allOf(List> futures,
BinaryOperator reducer,
T emptyValue) {
return Tools.allOf(futures)
.thenApply(resultList -> resultList.stream().reduce(reducer).orElse(emptyValue));
}
/**
* Returns a new CompletableFuture completed with the first result from a list of futures. If no future
* is completed successfully, the returned future will be completed with the first exception.
*
* @param futures the input futures
* @param future result type
* @return a new CompletableFuture
*/
public static CompletableFuture firstOf(List> futures) {
CompletableFuture resultFuture = new CompletableFuture<>();
CompletableFuture.allOf(futures.stream()
.map(future -> future.thenAccept(r -> resultFuture.complete(r)))
.toArray(CompletableFuture[]::new))
.whenComplete((r, e) -> {
if (!resultFuture.isDone()) {
if (e != null) {
resultFuture.completeExceptionally(e);
} else {
resultFuture.complete(null);
}
}
});
return resultFuture;
}
/**
* Returns a new CompletableFuture completed by with the first positive result from a list of
* input CompletableFutures.
*
* @param futures the input list of CompletableFutures
* @param positiveResultMatcher matcher to identify a positive result
* @param negativeResult value to complete with if none of the futures complete with a positive result
* @param value type of CompletableFuture
* @return a new CompletableFuture
*/
public static CompletableFuture firstOf(List> futures,
Match positiveResultMatcher,
T negativeResult) {
CompletableFuture responseFuture = new CompletableFuture<>();
Tools.allOf(Lists.transform(futures, future -> future.thenAccept(r -> {
if (positiveResultMatcher.matches(r)) {
responseFuture.complete(r);
}
}))).whenComplete((r, e) -> {
if (!responseFuture.isDone()) {
if (e != null) {
responseFuture.completeExceptionally(e);
} else {
responseFuture.complete(negativeResult);
}
}
});
return responseFuture;
}
/**
* Returns the contents of {@code ByteBuffer} as byte array.
*
* WARNING: There is a performance cost due to array copy
* when using this method.
*
* @param buffer byte buffer
* @return byte array containing the byte buffer contents
*/
public static byte[] byteBuffertoArray(ByteBuffer buffer) {
int length = buffer.remaining();
if (buffer.hasArray()) {
int offset = buffer.arrayOffset() + buffer.position();
return Arrays.copyOfRange(buffer.array(), offset, offset + length);
}
byte[] bytes = new byte[length];
buffer.duplicate().get(bytes);
return bytes;
}
/**
* Converts an iterable to a stream.
*
* @param it iterable to convert
* @param type if item
* @return iterable as a stream
*/
public static Stream stream(Iterable it) {
return StreamSupport.stream(it.spliterator(), false);
}
/**
* Converts an optional to a stream.
*
* @param optional optional to convert
* @param type of enclosed value
* @return optional as a stream
*/
public static Stream stream(Optional extends T> optional) {
return optional.map(x -> Stream.of(x)).orElse(Stream.empty());
}
// Auxiliary path visitor for recursive directory structure copying.
private static class DirectoryCopier extends SimpleFileVisitor {
private Path src;
private Path dst;
private StandardCopyOption copyOption = StandardCopyOption.REPLACE_EXISTING;
DirectoryCopier(String src, String dst) {
this.src = Paths.get(src);
this.dst = Paths.get(dst);
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
Path targetPath = dst.resolve(src.relativize(dir));
if (!Files.exists(targetPath)) {
Files.createDirectory(targetPath);
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.copy(file, dst.resolve(src.relativize(file)), copyOption);
return FileVisitResult.CONTINUE;
}
}
/**
* Creates OffsetDateTime instance from epoch milliseconds,
* using system default time zone.
*
* @param epochMillis to convert
* @return OffsetDateTime
*/
public static OffsetDateTime defaultOffsetDataTime(long epochMillis) {
return OffsetDateTime.ofInstant(Instant.ofEpochMilli(epochMillis),
ZoneId.systemDefault());
}
/**
* Returns smaller of the two Comparable values.
*
* @param l an argument
* @param r another argument
* @return the smaller of {@code l} or {@code r}
* @param Comparable type
* @throws NullPointerException if any of the arguments were null.
*/
public static > C min(C l, C r) {
checkNotNull(l, "l cannot be null");
checkNotNull(r, "r cannot be null");
return l.compareTo(r) <= 0 ? l : r;
}
/**
* Returns larger of the two Comparable values.
*
* @param l an argument
* @param r another argument
* @return the larger of {@code l} or {@code r}
* @param Comparable type
* @throws NullPointerException if any of the arguments were null.
*/
public static > C max(C l, C r) {
checkNotNull(l, "l cannot be null");
checkNotNull(r, "r cannot be null");
return l.compareTo(r) >= 0 ? l : r;
}
/**
* Log level for the customized logger.
*/
public enum LogLevel {
TRACE, DEBUG, INFO, WARN, ERROR
}
/**
* Wrapper function that enables logger invocation with log level as a parameter.
*
* @param logger logger
* @param level log level
* @param format format string
* @param args objects
*/
public static void log(Logger logger, LogLevel level, String format, Object... args) {
switch (level) {
case TRACE:
logger.trace(format, args);
break;
case DEBUG:
logger.debug(format, args);
break;
case INFO:
logger.info(format, args);
break;
case WARN:
logger.warn(format, args);
break;
case ERROR:
logger.error(format, args);
break;
default:
log.error("Unknown log level {}", level);
break;
}
}
}