docs: public static void copyDirectory(Path sourceDirectory, Path targetDirectory, Set<Path> pathsToSkip)
        throws IOException {
      requireNonNull(sourceDirectory);
      requireNonNull(targetDirectory);
      requireNonNull(pathsToSkip);
  
      Set<Path> absolutePathsToSkip =
          pathsToSkip.stream().map(path -> path.toAbsolutePath().normalize()).collect(toSet());
  
      Files.walkFileTree(sourceDirectory, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
        new FileVisitor<Path>() {
          @Override
          public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes sourceBasic) throws IOException {
            if (absolutePathsToSkip.contains(dir.toAbsolutePath().normalize()))
              return FileVisitResult.SKIP_SUBTREE;
  
            Path targetDir = Files.createDirectories(targetDirectory.resolve(sourceDirectory.relativize(dir)));
            AclFileAttributeView acl = Files.getFileAttributeView(dir, AclFileAttributeView.class);
  
            if (acl != null)
              Files.getFileAttributeView(targetDir, AclFileAttributeView.class).setAcl(acl.getAcl());
  
            DosFileAttributeView dosAttrs = Files.getFileAttributeView(dir, DosFileAttributeView.class);
  
            if (dosAttrs != null) {
              DosFileAttributes sourceDosAttrs = dosAttrs.readAttributes();
              DosFileAttributeView targetDosAttrs = Files.getFileAttributeView(targetDir, DosFileAttributeView.class);
              targetDosAttrs.setArchive(sourceDosAttrs.isArchive());
              targetDosAttrs.setHidden(sourceDosAttrs.isHidden());
              targetDosAttrs.setReadOnly(sourceDosAttrs.isReadOnly());
              targetDosAttrs.setSystem(sourceDosAttrs.isSystem());
            }
  
            FileOwnerAttributeView ownerAttrs = Files.getFileAttributeView(dir, FileOwnerAttributeView.class);
  
            if (ownerAttrs != null) {
              FileOwnerAttributeView targetOwner = Files.getFileAttributeView(targetDir, FileOwnerAttributeView.class);
              targetOwner.setOwner(ownerAttrs.getOwner());
            }
  
            PosixFileAttributeView posixAttrs = Files.getFileAttributeView(dir, PosixFileAttributeView.class);
  
            if (posixAttrs != null) {
              PosixFileAttributes sourcePosix = posixAttrs.readAttributes();
              PosixFileAttributeView targetPosix = Files.getFileAttributeView(targetDir, PosixFileAttributeView.class);
              targetPosix.setPermissions(sourcePosix.permissions());
              targetPosix.setGroup(sourcePosix.group());
            }
  
            UserDefinedFileAttributeView userAttrs = Files.getFileAttributeView(dir, UserDefinedFileAttributeView.class);
  
            if (userAttrs != null) {
              UserDefinedFileAttributeView targetUser =
                  Files.getFileAttributeView(targetDir, UserDefinedFileAttributeView.class);
              for (String key : userAttrs.list()) {
                ByteBuffer buffer = ByteBuffer.allocate(userAttrs.size(key));
                userAttrs.read(key, buffer);
                buffer.flip();
                targetUser.write(key, buffer);
              }
            }
  
            // Must be done last, otherwise last-modified time may be wrong
            BasicFileAttributeView targetBasic = Files.getFileAttributeView(targetDir, BasicFileAttributeView.class);
            targetBasic.setTimes(sourceBasic.lastModifiedTime(), sourceBasic.lastAccessTime(), sourceBasic.creationTime());
  
            return FileVisitResult.CONTINUE;
          }
  
          @Override
          public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            if (absolutePathsToSkip.contains(file.toAbsolutePath().normalize()))
              return FileVisitResult.SKIP_SUBTREE;
  
            Files.copy(file, targetDirectory.resolve(sourceDirectory.relativize(file)),
              StandardCopyOption.COPY_ATTRIBUTES);
  
            return FileVisitResult.CONTINUE;
          }
  
          @Override
          public FileVisitResult visitFileFailed(Path file, IOException e) throws IOException {
            throw e;
          }
  
          @Override
          public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
            if (e != null)
              throw e;
  
            return FileVisitResult.CONTINUE;
          }
        });
    }