diff --git a/src/main/java/me/itzg/helpers/errors/ExceptionDetailer.java b/src/main/java/me/itzg/helpers/errors/ExceptionDetailer.java new file mode 100644 index 00000000..8023cf81 --- /dev/null +++ b/src/main/java/me/itzg/helpers/errors/ExceptionDetailer.java @@ -0,0 +1,41 @@ +package me.itzg.helpers.errors; + +import java.util.ArrayList; +import java.util.Iterator; + +public class ExceptionDetailer { + + private static class CauseIterator implements Iterator { + private Throwable current; + + CauseIterator(Throwable initial) { + this.current = initial; + } + + @Override + public boolean hasNext() { + return current != null; + } + + public Throwable next() { + if (current == null) { + return null; + } + Throwable result = current; + current = current.getCause(); + return result; + } + } + + public static String buildCausalMessages(Throwable throwable) { + final CauseIterator causeIterator = new CauseIterator(throwable); + final ArrayList parts = new ArrayList<>(); + while (causeIterator.hasNext()) { + final Throwable cause = causeIterator.next(); + if (cause != null) { + parts.add(cause.getClass().getSimpleName() + ": " + cause.getMessage()); + } + } + return String.join("; ", parts); + } +} diff --git a/src/main/java/me/itzg/helpers/sync/SynchronizingFileVisitor.java b/src/main/java/me/itzg/helpers/sync/SynchronizingFileVisitor.java index 07b0e293..43932ea0 100644 --- a/src/main/java/me/itzg/helpers/sync/SynchronizingFileVisitor.java +++ b/src/main/java/me/itzg/helpers/sync/SynchronizingFileVisitor.java @@ -9,6 +9,8 @@ import java.nio.file.attribute.FileTime; import java.util.List; import lombok.extern.slf4j.Slf4j; +import me.itzg.helpers.errors.ExceptionDetailer; +import me.itzg.helpers.errors.InvalidParameterException; @Slf4j class SynchronizingFileVisitor implements FileVisitor { @@ -24,9 +26,21 @@ public SynchronizingFileVisitor(Path src, Path dest, boolean skipNewerInDestinat this.fileProcessor = fileProcessor; } + /** + * Convenience method that processes each source path into the destination path by applying a {@link SynchronizingFileVisitor} + * to each source. + * @param srcDest source... dest + * @param skipNewerInDestination provided to {@link SynchronizingFileVisitor} + * @param fileProcessor provided to {@link SynchronizingFileVisitor} + * @return exit code style of 1 for failure, 0 for success + */ static int walkDirectories(List srcDest, boolean skipNewerInDestination, FileProcessor fileProcessor ) { + if (srcDest.size() < 2) { + throw new InvalidParameterException("At least one source and destination path is required"); + } + // TODO can use getLast() with java 21 final Path dest = srcDest.get(srcDest.size() - 1); @@ -35,7 +49,7 @@ static int walkDirectories(List srcDest, boolean skipNewerInDestination, try { Files.walkFileTree(src, new SynchronizingFileVisitor(src, dest, skipNewerInDestination, fileProcessor)); } catch (IOException e) { - log.error("Failed to sync and interpolate {} into {} : {}", src, dest, e.getMessage()); + log.error("Failed to sync and interpolate {} into {}: {}", src, dest, ExceptionDetailer.buildCausalMessages(e)); log.debug("Details", e); return 1; } @@ -104,7 +118,7 @@ private boolean shouldProcessFile(Path srcFile, Path destFile) throws IOExceptio @Override public FileVisitResult visitFileFailed(Path file, IOException e) { - log.warn("Failed to visit file {} due to {}:{}", file, e.getClass(), e.getMessage()); + log.warn("Failed to visit file {} due to {}", file, ExceptionDetailer.buildCausalMessages(e)); log.debug("Details", e); return FileVisitResult.CONTINUE; }