code: public Zips addFile(File file, boolean preserveRoot, FileFilter filter) {
      if (!file.isDirectory()) {
        this.changedEntries.add(new FileSource(file.getName(), file));
        return this;
      }
      
      Collection<File> files = ZTFileUtil.listFiles(file);
      for (File entryFile : files) {
        if (filter != null && !filter.accept(entryFile)) {
          continue;
        }
        String entryPath = getRelativePath(file, entryFile);
        if (File.separatorChar == IOUtils.DIR_SEPARATOR_WINDOWS) {
          // replace directory separators on windows as at least 7zip packs zip with entries having "/" like on linux
          entryPath = entryPath.replace(IOUtils.DIR_SEPARATOR_WINDOWS, IOUtils.DIR_SEPARATOR_UNIX);
        }
        if (preserveRoot) {
          entryPath = file.getName() + entryPath;
        }
        if (entryPath.startsWith("/")) {
          entryPath = entryPath.substring(1);
        }
        this.changedEntries.add(new FileSource(entryPath, entryFile));
      }
      return this;
    }
docs: Adds a file entry. If given file is a dir, adds it and all subfiles recursively.
  Adding takes precedence over removal of entries.
  
  @param file file to add.
  @param preserveRoot if file is a directory, true indicates we want to preserve this dir in the zip.
  otherwise children of the file are added directly under root.
  @param filter a filter to accept files for adding, null means all files are accepted
  @return this Zips for fluent api